Delphi RSS Resources, Delphi Components, Delphi Sites, Articles, Dynamic XML Feeds, Tutorials, Sources
 
 
 
Google
 
Web delphirss.com
| Server-Scripts.com | Informations for JAVA | Informations for PHP | SEO Web Links | Borland Delphi
 

VCL

Vertical scrollbar in the TDBGrid
Create colored panels of various sizes and form positions at run time
Main difference between tabsets with notebooks and TabbedNoteBook
Missing visual cursor in a TStringGrid
Lost the highlight color for the selected cell in a TStringGrid
Include a combo box in a TDBGrid
Dynamically identifiers for checkboxes
Create listbox on runtime
Store variables in a listbox
Name property during runtime
Handle click for several buttons, using the caption
Text in vertical direction
Displaying memo field in DBGrid
Multiselect in a stringgrid
Center text in cells of TStringGrid
Change the number of tabs in a TTabSet at runtime
CheckBox array - using common event
Right-editing in TEdit and columns of TStringGrid
More than one line in a cell of TStringGrid
Events for runtime created components
Component Creation
CheckBox array, how to use common event
Shared Controls on a TTabbedNotebook
Screen sizes and stringgrids
Aligning cells in stringgrid
Coloured StringGrid
Testing for the existence of a Component Property
Set event-handler at run-time
Application events
ReleaseDC and TCanvas
Using TStringList in a visual component
#0 KeyPress
StringGrid Masks
StringGrid right alignment
FileName property in non-visual component
SendMessage and TLabel
Call the Hint method directly
Changing in TOpenDialog
Edit in StringGrid
How to detect a row focus change in TDBGrid
Combobox problems
Change Grid Cell Color
TEdit and OnEnter event
How do I create a component like TField
Accessing notebook pages
Change delete behavior in Memo
Listbox with graphic
Masked Find in TStringList
Dual list box
OnClose proc
Colouring fields in DBGRIDS
"Autosizing" StringGrids
Right aligning menus
Which button on panel is the Sender
Publishing properties that are sets
Right Text Alignment in edit box
Multi line Hints
TTimer question
Make TAB act like ENTER in StringGrid
Cycle through list of components
Splitter bar
Popup menu in dependence on mouse position
TabbedNotebook and common components on all pages
How to disable a tab(page) in a Notebook component
Insert text in MEMO
Name of the item in a TListBox
Sync'ing Tabset with Listbox
TDBGrid - Vertical Scrollbar
Variable control-names
OnkeyDown and Hot-key problem
Add an OnClick event to DBGrid
Events for components created at Run-Time
Alignment in Listbox
Two columns in DBLookupComboBox
Change the color of a grid cell in a TDBGrid
Cells' Position on DBGrid
Listbox - OnChange
TabbedNotbook enable / disable one page
SETFOCUS in the StringGrid
Different colors in DBGrid
Cursor Pos in TRichEdit
Trapping OnEnter in my component
Display popup menu
Canvas.TextWidth
TDBNavigator buttons
Tabbed Notebook and visible components at several pages
How to empty a DBEdit
OnDraw Event for TStringGrid
Hiding TabbedNoteBook Pages
Accessing memo field data
Word manipulation in TStringGrid
Restrict the length of a TStringGrid field
TextOut to a control's parent
StatusBar - How to display Clock/Date/Keyboard Status
Icons in a Popup menus
Accessing Components in a TGroupBox
Incrementing String Field
Using OnHint events among multiple forms
Moving to a tab by name on a tabset
Control font styles
Removing the vertical scrollbar from a TDBGrid
Getting a device context for a control
Disabling DBNavigator buttons
DBGrid that shows images
Using canvas in user-defined components
Accessing other components from a base component
Extending DBGrid
Creating a resizeable (elastic) panel
Assigning OnClick events for menu items created at run-time
Setting boundaries for newly created controls
Expanding a path to a TUutlineNode referenced by index
Associating a string with each component
Populating TDBComboBoxes and TDBListBoxes
Activating horizontal scrollbar for listboxes
Click and move components at run-time
Validating input in TEdit components
Different colored characters in a string grid
ISBN validation
Selecting multiple records in TDBGrid
Moving in a TMemo Field
Copy of component properties
TMenuItem - create and add an event at runtime
Scrolling a TRichEdit control
Mask Edit
List Box Horizontal Scroll
ComboBox dropdown
TProgressBar in TStatusPanel
TTreeView slow down


Vertical scrollbar in the TDBGrid

Question

I've noticed that the vertical scroll bar in the TDBGrid doesn't
behave as it should (cannot guage the current record's position in
the table by the scroll bar).  I would  like to know if there is any
way to get the scroll bar to work like the one in the Paradox TableFrame.

Answer

A:
Well.... yes and no.  The problem with the scroll bar is that not all
data formats (in fact, almost none) support record order No's.  In
other words, there is no unique identifier within a table that
specifies where in the table a record appears.  For example in dBase
tables, the records have a recNo based upon the order they were
entered.  If you put an index on the fields, these numbers are totally
out of whack.  SQL tables & queries on the other hand has never heard
of a record order no and never will.  Paradox tables are the only ones
that include a logical sort order no in each index that they create.
For whatever reason, Borland choose to make the scroll bar behave the
same regardless of whether or not you were using Paradox tables.  You
can get around this by turning off the scroll bars and using a
TScrollBar component to simulate the behaviour that you want.


Create colored panels of various sizes and form positions at run time

Question

I'm trying to make a type of time line display form a shedule system. What I
need to do is create Colored panels (tpanels) of various sizes and form
positions at run time and attach an onclick method to them.
I have tried to make an array of Tpanel and create the panels but after
setting valid sizes and colors and callen show, I can't get them to appear
on screen.

Answer

A:
This is just a guess without seeing your code, but did you set the parent of
the panels? You NEED the following two lines in the OnCreate event of the
form to display a control on that form :

MyPanel := TPanel.Create(Self);
MyPanel.Parent := Self;


Main difference between tabsets with notebooks and TabbedNoteBook

Question

Can someone tell me what the main difference is between using these
two types of components. Tabsets with notebooks and TabbedNoteBook
component?  Is one a lot heavy on resources?  I suspect that the
Tabbed Notebook would be since it does everything for you.

Answer

A:
As I see it, the Tabbed Notebook
+ is easier to work with (design time) thanks to the integration of the
  tabset and the notebook
- is lacking the option to place the tabset where you like
Considering that Borland (a) within Delphi mostly uses the bottom-attached
style, and (b) didn't supply the sources even in the full VCL source kit,
one might wonder a bit, but I'll leave that to you.
The Orpheus VCL components has a "tabbed multi-page form" using which you
may put the tabset where you like plus more features.

A:
Resource-wise, they appear to be virtually identical. The biggest difference
(other than the obvious aesthetic difference in terms of the placement of the
tabs) is that you are forced to have all tabs visible when using the
TabbedNotebook. When using the Tabset and Notebook combination, you can allow
horizontal scrolling to show additional tabs (a la Component Palette).
Depending upon the situation, that may be preferable.


Missing visual cursor in a TStringGrid

Question

I have the options in a TStringGrid set to the same values of another
TStringGrid (separate app). When I run the app and try to edit one of
the cells, I don't get a visual cursor.

Answer

I had the same problem and it took forever to find it. If DefaultRowHeight
<= 14, the visual cursor disappears!  My solution was to set DefaultRowHeight
to 15.


Lost the highlight color for the selected cell in a TStringGrid

Question

I have a StringGrid component with an over-rided OnDrawCell event. The problem is that I have lost the Highlight color for the selected cell(s).
What do I need to add to gain this back?

Answer

A:
Obviously you have changed DefaultDrawing to false.  This is a bad
move, in my opinion, since you lose almost all the nice drawing that
the TGrids take care of for you. When I created my TWrapGrid, I
initially turned it off, colored the grids the way I wanted, but then
realized that it was better to just output text and let
DefaultDrawing handle the rest.
If you really insist on coloring your cells differently AND have the
highlight color for the selected cell, you should take a look at the
Grids source code and extract it from there.


Include a combo box in a TDBGrid

Question

I have in my app a TDBGrid, and one of their fields is a selection of predetermined values (like a boolean one, with True or False). I want to put a combo box in that line to give a more confortable way to the user to enter the data. How can i obtain that?

Answer

A:
Basically what you want to accomplish is
1. Create and draw a TComboBox every time you enter one of the cells
in your grid in that column
2. Get the current value from the field (if any) and put it into the
CB
3. After done, put the new value from the CB into the field
4. get rid of the CB


Dynamically identifiers for checkboxes

Question

My code looks something like this...
  if CheckBox(var).checked=True then.... 
  where (var) is a counter in a for loop.
Is the number of checkboxes not known when coding ? ie created only at run
time?

Answer

A:
When in design mode, you (the programmer) really should know how many
checkboxes are on a given form.  When the App is running...
Use Delphi's Run Time Type Information (RTTI).
For a given form, try the following code snippet:

var
   i : Integer
begin
   for i := 0 to ComponentCount - 1 do:
      if Components[i] is TCheckBox then
	 (Components[i] as TCheckBox).Checked then
	 begin
	    ... insert your code here ...
	 end;
end;

In addition, the following code is a valid statement in Delphi:

	if Components[i] = CheckBox5 then
	   DoSomething;

Also, each component in Delphi has a Published Property called 'Tag',
you can use this to your advantage by setting the Tag to some non-zero
number at design time, then using it at run-time, ie:

var
   i : Integer
begin
   for i := 0 to ComponentCount - 1 do:
      if Components[i] is TCheckBox then
      with (Components[i] as TCheckBox) do
	 Case Tag of
	    1 : if Checked then DoSomethingOnBox1;
	    2 : if Checked then DoSomethingOnBox2;
	    ... etc ...
	 end;
end;

For more info, keyword search Delphi's help on "ComponentCount".


Create listbox on runtime

Question

I'll like to create a list box dynamically at run-time that fills half the size of the form ..... and I'll like it to be adjusted too when the form resizes (so that there will be no scroll-bars on the form).
How can I determine the size of the form and create this list box dynamically?  also, can list boxes take more than 5,000 items ??

Answer

A:
Setting the ListBox alignment to alLeft will cause the ListBox to be resized
whenever the form is resized. Setting the width is easy (remember that the
Width you see on the right side of the assignment is the Form's
Width property).

The number of elements that a ListBox will hold is limited only by available 
memory.

procedure TForm1.CreateListBox;
var
   LB : TListBox;
begin
   LB := TListBox.Create;
   LB.Align := alLeft;
   LB.Width := Width div 2;
end;

A:
Here's logic that creates the list box dynamically and resizes it when the
window is resized.  I hope it is helpful.  Also, I believe that a listbox is
limited to 32K of data.

unit Unit1;

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs,
  StdCtrls  { you'll need this for the ListBox }  ;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormResize(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  listbox: TListBox ;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
begin
     listbox := TListBox.Create(self) ;
     listbox.Parent := self ;
     listbox.Top := 0 ;
     listbox.Left := 0 ;
     listbox.Width := self.Width div 2 ;
     listbox.Height := self.Height div 2 ;
     listbox.items.add('test 1') ;
     listbox.items.add('test 2') ;
     { etcetera ... }
end;

procedure TForm1.FormResize(Sender: TObject);
begin
     listbox.Width := self.Width div 2 ;
     listbox.Height := self.Height div 2 ;
end;

end.


Store variables in a listbox

Question

In a delphi listbox is there any way to store a variable with each entry (in a sorted list box).

Answer

A:
A TListBox has a TStringList to store the strings. TStringList conatins an items property (or something like that) that stores a pointer (or anything the size of a pointer or smaller). So, for example, you can store an object, component, record, or a variable to correspond to each string in the
TListBox.


Name property during runtime

Question

It looks like Sender is looking at the value in the 'Name' property.  Is there a way to map the value in Sender to a component type ( such as TEdit, TCheckbox, Tlabel, etc. )?

Answer

A:
If ( Sender is TLabel ) then..
or if you know it is going to be something you can
TLabel( Sender ).Caption
                                                                                                                             A:                                                                                                                         To access the name property, use the either one of the following
examples:

formname:=(myform as tobject).name;

or

formname:=(myform as tcontrol).name;

this should do it for you. The reason why delphi does not want you to
do this is because it has certain controls that do not have specific
names and therefore, the compiler would have to check each call to
see if it is valid.

A:
If you use the ClassName property like this.

   with Sender as TForm do
      Label1.Caption := copy(ClassName,2,length(ClassName)-1);

This will give the desired effect without extra coding in the Form's create
method.

A:
'Sender' may well not be the form in question, and your program will throw an
exception on the invalid typecast. I'm not sure under which conditions (if
any) Sender actually would be the Form itself.

Anyway, you can protect yourself by bracketing the call with a exception
handler or just a simple test, like so:
  If Sender is TForm then
    Label1.Caption := (Sender as TForm).Name ;

If what you're trying to accomplish is the following:
  Label1.Caption := Form1.Name ;

That's a whole different kettle of fish. I've read a lot of complaints
that this or that property of a Form isn't available at run time, most
of the cases seem to be linked to a misapprehension regarding class
initialization. If you read the Delphi docs carefully, you'll note that
setting a property in the Object Inspector does NOT automatically set
that property for run-time purposes.  The answer to THIS situation is to
explicitly set the property (.Name in this case) in the Form's .Create
method. So, some code like the following WILL work:

procedure TForm1.Create( Sender : TObject ) ;
begin
  Form1.Name := 'Form1' ;
end ;

procedure TForm1.Button1Click( Sender : TObject ) ;
begin
  Label1.Caption := Form1.Name ;
end ;

A:
var
  TC: TComponent;
begin
  TC := label1.Owner;
  label1.Caption := TC.ClassName;
end;

A:
I added a button to my form and in its event handler I put:

	name := 'AName';

Then after clicking on the button, I could then click on the form and
the label's caption changed to 'AName'.
The solution is to define the property Name in the form's create event.
I.E. if you have a form named MyForm then in its OnCreate event you
should have:

	name := 'MyForm';

This will solve your problem, but I agree its a little abnoxious.

Handle click for several buttons, using the caption

Question

I want to write one piece of code to handle click for several buttons, using
the caption (or name) property of each button.
I can't, have had to use a long series of IFs with specific names for each
button: IF sender = button1 THEN... ...button1.caption....button2...button.
This is very inelegant, and I'm sure there's a cleaner way of doing it.

Answer

A:
It sounds like you have, but, first make sure that you have the OnClick
event for each button on the calculator ( numeric buttons 0..9 ) pointing a
common event handler.

In the common event handler for the numeric buttons extract the button
caption with something like:

	Edit1.Text := TButton(Sender).Caption;

A:
In this case I think it would be perfect to use the Tag property of each
button:
1) assign a unique Tag to each button (e.g. it's Arabic equivalence)
2) procedure TForm1.Button1Click(Sender: TObject);
   begin
     if (Sender is TButton) then
       with (Sender as TButton) do
	 {use Tag}
   end;

A:
If all you want is the caption, then there is indeed a very elegant way
to do it. Attach this event handler to all of your buttons and use typecasting:

procedure TForm1.Edit1Click(Sender: TObject);
begin
     edit1.text := (sender as TButton).caption ;
end;
It will not suffice to say:

	sender.caption

because the compiler has no way of knowing if "sender" will have a caption
property.


Text in vertical direction

Question

I am in need of displaying text in the vertical direction from either upward - downward or downward - upward directions, as well as inclined texts (that is, in non-horizontal directions). How could I do it?

Answer

A:
var
Hfont: Thandle;
logfont:TLogFont;
font: Thandle;
count: integer;
begin
       LogFont.lfheight:=30;
       logfont.lfwidth:=10;
       logfont.lfweight:=900;
       LogFont.lfEscapement:=-200;
       logfont.lfcharset:=1;
       logfont.lfoutprecision:=out_tt_precis;
       logfont.lfquality:=draft_quality;
       logfont.lfpitchandfamily:=FF_Modern;

       font:=createfontindirect(logfont);

       Selectobject(Form1.canvas.handle,font);

       SetTextColor(Form1.canvas.handle,rgb(0,0,200));
       SetBKmode(Form1.canvas.handle,transparent);

       {textout(form1.canvas.handle,10,10,'Rotated',7);}

for count:=1 to 100 do
    begin
	 canvas.textout(Random(form1.width),Random(form1.height),'Rotated');
       
SetTextColor(form1.canvas.handle,rgb(Random(255),Random(255),Random(255)));
    end;

deleteobject(font);

end;


Displaying memo field in DBGrid

Question

I'm trying to display the first 100 bytes of a TMemoField in a DBGrid. To do
that, I created a calculated string field and tried the following code:

procedure TfrmDiario.Query1CalcFields(DataSet: TDataset);
var
  s : String;
  p : ^Char;
begin
  with Query1Historico do begin   {Query1Historico is the TMemoField}
    if DataSize>0 then begin
      GetMem(p, DataSize);
      GetData(p);
      StrCopy(PChar(p), @(s[1]));
      s[0] := #100;
      FreeMem(p, DataSize)
    end
    else
      s := '';
    Query1StrHistorico.AsString := s;  {Query1StrHistorico is the calculated
                                        TStringField}
  end;
end;

The problem is that Query1Historico.DataSize is ALWAYS zero! I even
tried Query1Historico.SaveToFile and it indeed created a zero length file.

Answer

A:
I also finally loose the hope to see TMemoField.DataSize has another value
than zero. Maybe meaning of the DataSize is size of the part of Memo field
which saved in the .db file. Instead of this I using now the TBlobStream
object which works perfectly. It looks like :

Var
        pBuffer	: PChar ;
        Blob	: TBlobStream ;
begin
        {FDataField is the TMemoField}
        Blob := TBlobStream.Create( FDataField, bmRead ) ;
        try
	if Blob.Size > 0 then
        	try
            	       GetMem( pBuffer, Blob.Size ) ;
	       Blob.Read( pBuffer^, Blob.Size ) ;
{                        do something    }
                     FreeMem( pBuffer, Blob.Size ) ;
            except
                ShowMessage( 'Out of memory' );
            end ;
       finally
    	Blob.Free
      end ;


Multiselect in a stringgrid

Question

I have just started to work with the stringgrid component and want to try
to do a multiselect (as in a listbox). This is where a user can select
multiple (non-adjacent) rows or cells and copy some data from an editbox or
combobox to them. The help and literature on this are very sparse.
Is this possible or is there a component available which does this?

Answer

A:
I did the same thing with a DBGrid. (Not implemented the Shift-MouseDown,
Ctrl-MouseDown staff yet).
For the TStringGrid I think the idea is as following :
1. Then you filling the grid set the Objects[0, ARow] with a some
Boolean object
        like TBooleanObject = class(TObject)
                public
                        Flag    : Boolean ;
                end ;
2. In OnMouseDown and OnKeyDown events change the flag as needed.
3. In OnDrawCell event draw the row according to Objects[0,ARow] flag.


Center text in cells of TStringGrid

Question

How justify the text inside the cell of a grid?

Answer

A:
Supply your own drawcell method here's an example that we've used.

procedure Tsearchfrm.Grid1DrawCell(Sender: TObject; Col, Row: Longint;
  Rect: TRect; State: TGridDrawState);
var l_oldalign : word;

begin
  if (row=0) or (col<2) then
    grid1.canvas.font.style:=grid1.canvas.font.style+[fsbold]; {set the headings in bold}

  if col<>1 then
   begin
 l_oldalign:=settextalign(grid1.canvas.handle,ta_right);
 {NB use the righthand side of the drawing rectangle}
 grid1.canvas.textrect(rect,rect.right-2, Rect.top+2,grid1.cells[col,row]);
       settextalign(grid1.canvas.handle,l_oldalign);
   end
  else
   begin
     grid1.canvas.textrect(rect,rect.left+2,rect.top+2,grid1.cells[col,row]);
   end;

  grid1.canvas.font.style:=grid1.canvas.font.style-[fsbold];

end;

Change the number of tabs in a TTabSet at runtime

Question

I want to be able to change the number of tabs in a TTabSet at runtime.
According to the documentation, this can be done by adding/deleting strings to
the 'Tabs' property.

Answer

A:
Assuming that somewhere in your code is a line like:

  TabSet1: TTabSet;  { assume it is in form Form1 }

then the code that you need to clear all of the tab labels is:

  Form1.TabSet1.Tabs.Clear;

In order to add a new tab label just use this code:

  Form1.TabSet1.Tabs.Add('some label');

Please note that I have only qualified the name on the assumption
that you are referencing it from within the same unit where it is
defined [but ignored the possibility that it might be in an event
handler or method that might partially qualify it -- the extra
qualification won't hurt]. If you need to refer to it from some
other unit then add the unit name (and add unit to "uses"), e.g.

  Unit1.Form1.TabSet1.Tabs.Add('a label');

Since "TabSet1.Tabs" is of type TStrings you can also use all of
the methods (AddObject, LoadFromFile, et cetera) that apply to
the TStrings type on that property.

CheckBox array - using common event

Question

In VB I had an array of CheckBox's that used a common event handler.
I could determine and handle which CheckBox caused the event by using 
the INDEX argument passed to the event handler.

Answer

A:
Try putting the checkboxes into a TGroupBox component. At run-time (or design 
time) assign the common procedure to the Click event of all the checkboxes.
You could use the Controls array property of TGroupBox to iterate through the 
child TCheckBoxes (and you could typecast them into TCheckBox).  
Something like:
  for i := 0 to GroupBox1.ControlCount -1 do
    if (GroupBox1.Controls[i] as TCheckBox).checked then
       {do something};

A:
You can get the name of the sender as follows:

procedure TMain1.CheckBoxClick(Sender: TObject);
var
     whodidit  : string[63];

begin
     whodidit := TComponent(sender).name;
     end; 


By casting you can get other propeties as well. For instance the tag
property can be very
useful. You can set each checkbox with an ID number at creation time. You
can read the ID at the event handler to identify the sender.

Right-editing in TEdit and columns of TStringGrid

Question

Right-editing in TEdit and columns of TStringGrid

Answer

A:
8.  It seems to me that this could be handled in one of 2 ways:

8.1   Editing raw unformatted numbers on a left-justified basis,
      then redisplaying with formatting and right justification
      when the focus shifts away from the control in question.
      This would require us to switch ES_MULTILINE/ES_RIGHT on
      and off as the focus changes.

8.2   By on-the-fly reformatting as the user enters digits.  In
      my experience, this would require the Edit Control to have
      a callback function for the "real-time" reformatting and
      display of the partially edited results.  I come from
      the Macintosh world, so I need help in establishing whether
      MS Edit Controls support such a callback function.

A:
It has been bugging me that there seems to be no simple way of achieving right
-justified editing without setting the Windows Edit Control style to
ES_MULTILINE as well as ES_RIGHT. (Re-writing the whole MS Edit Control
would be a non-trivial task).

More than one line in a cell of TStringGrid

Question

Can i write more than one line in a cell? How make it?

Answer

A:
Yes but you have to overwrite the OnDraw Event.
here's a sample that has a multiple line heading which gets centered and
"Bolded"

procedure TForm1.grid1DrawCell(Sender: TObject; Col, Row: Longint;
  Rect: TRect; State: TGridDrawState);

 var l_oldalign : word;
     l_YPos,l_XPos,i : integer;
     s,s1 : string;
     l_col,l_row :longint;

begin
  l_col := col;
  l_row := row;
  with sender as tstringgrid do
  begin
    if (l_row=0) then
      canvas.font.style:=canvas.font.style+[fsbold];
    if l_row=0 then
    begin
      l_oldalign:=settextalign(canvas.handle,ta_center);
      l_XPos:=rect.left + (rect.right - rect.left) div 2;
      s:=cells[l_col,l_row];
      while s<>'' do
      begin
        if pos(#13,s)<>0 then
        begin
          if pos(#13,s)=1 then
            s1:=''
          else
          begin
            s1:=trim(copy(s,1,pred(pos(#13,s))));
            delete(s,1,pred(pos(#13,s)));
          end;
          delete(s,1,2);
        end
        else
        begin
          s1:=trim(s);
          s:='';
        end;
        l_YPos:=rect.top+2;
        canvas.textrect(rect,l_Xpos,l_YPos,s1);
        inc(rect.top,rowheights[l_row] div 3);
      end;
      settextalign(canvas.handle,l_oldalign);
    end
    else
    begin
       canvas.textrect(rect,rect.left+2,rect.top+2,cells[l_col,l_row]);
    end;

    canvas.font.style:=canvas.font.style-[fsbold];
  end;
end;

Events for runtime created components

Question

That is I created new component inherited from TSpeedbutton. This new button
is created runtime and that is OK, I can change it's properties etc. The
problem is I don't seem to get the idea how to respond to it's events because
at design time the component doesn't exist and therefor Object Inspector is
worthless in this case.

Answer

A:
Here's some code I wrote just now, I used a new project, with a button and a
menu.
(If you like, just create a new project, and add a button and a menu ... :)

{The procedure to create a new menu item ...}
procedure TForm1.Button1Click(Sender: TObject);
var
  NewItem: TMenuItem;
begin
  NewItem := TMenuItem.Create(Form1);
  NewItem.Caption := 'Dynamic Item ...';
  NewItem.OnClick := xyz;
  MainMenu1.Items.Insert(0, NewItem);     <- Note: have a look at the Delphi
end;                                               example for Insert ...

{Any old 'xyz' procedure (can also be a currently defined one eg
Form1.DblClick)}
procedure TForm1.xyz(Sender: TObject);
begin
  showmessage('Running this procedure !!');
end;


Note : If you use an un-defined procedure, you'll need to declare it.
       I did mine right at the top in the form's type declaration like so:

           private
             { Private declarations }
           public
             { Public declarations }
             procedure xyz(Sender: TObject);   <- Events other than Form1 can
                                                  access the code here ...

A:
Just set the event handler property (eg. OnClick, OnDblClick, OnMouseDown etc)
to the procedure which you have written to handle the events. You need to make
sure that the parameters match those expected by the specific event handler.
eg.
 MySpeedButton.OnClick := MyClickEventHandler;

where...

procedure MyClickEventHandler(Sender: TObject);
begin

end;

Component Creation

Question

I am creating a component that has the following declaration:
I want to publish the properties of the A, B and C panels to allow editing in
the property editor and I want to group them under an expandable group sort
of the way the Font property works in the property editor. What is the best way
to implement this?

Answer

A:
To group them like the Font property you need to create a TPersistent
subclass. For example:

TBoolList = class(TPersistent)
  private
    FValue1: Boolean;
    FValue2: Boolean
 published
   property Value1: Boolean read FValue1 write FValue1;
   property Value2: Boolean read FValue2 write FValue2;
end;

Then in you new component create a ivar for this subclass. You =must=
override the constructor for this to work properly.

TMyPanel = class(TCustomPanel)
  private
    FBoolList: TBoolList;
 public
   constructor Create( AOwner: TComponent ); override;
 published
   property BoolList: TBoolList read FBoolList write FBoolLisr;
end;

Then add this code in your constructor:

constructor TMyPanel.Create( AOwner: TComponent );
begin
  inherited Create( AOwner );
  FBoolList := TBoolList.Create;
end;

CheckBox array, how to use common event

Question

In VB I had an array of CheckBox's that used a common event handler.
Using common event?

Answer

A:
var
	CheckArray: array[1..x] of TCheckBox;
	i:integer;
begin
  for i:=1 to x do begin
    CheckArray[i]:=TCheckBox.Create(Form1);
    {Set properties}
    with CheckBox[i] do begin
      Left:=i*20;
      Width:=15;
      etc...
    end;
  end;

Apparently you can say    Check[i].OnClick:=xyz.

Don't know that part of it myself yet. Dynamic creation of components yes, but
of event handlers?

A:
There is a way to have an array of checkboxes with a common event handler.
You must place them in the form and give contiguous names
(Check1,Check2, etc.). Then set a common event handler to them. The
event handler should be like this:
    procedure TForm.Check1Click(Sender : TObject);
    var
      i : Integer;
    begin
      for i := 1 to 10 { assuming you have 10 checkboxes } do
        With TCheckBox(FindComponent('Check'+IntToStr(i))) do begin
          { do some stuff }
        end;
    end;

Shared Controls on a TTabbedNotebook

Question

How do I get Controls on a TTabbedNotebook to be shared among all pages or a
subset thereof?

Answer

A:
You could give the same impression by putting the controls into a panel or
some other holder wich is not a child of the notebook but is on top. As it
is not a child - changing the tabs will have no effect other than what code
you want to control it.  Visibly it gives the impression that each tab /
notebook has the same page of controls.

A:
What I do is place the shared controls on the Form rather than
on a particular page of the TTabbedNotebook.  To do this, you
need to set the TTabbedNotebook's Align property to something
other than alClient -- e.g. if the common controls are towards
the bottom of the Form, use alTop for the TTabbedNotebook, and
then move the bottom of the TTabbedNotebook up a bit from the
bottom of the Form, leaving a gap for the "shared" controls.

I you want to make the "shared" controls _look_ as if they are
on each page of the TTabbedNotebook, leave them on the Form,
but Bring them to the Front, and then move the bottom of the
TTabbedNotebook back down to the bottom of the Form.

This works for me. I havn't yet had a need for shared
controls on a _subset_ if the pages, but my first idea would
be to change the Visible property of such Controls to
true/false in the TTabbedNotebook's OnChange event.

Screen sizes and stringgrids

Question

I have an application with several screens. The application runs on
several PC's with various display capabilities. If I set the scale
property of the form to True then some of my stringgrids do not display
correctly, and some of my text won't fit like it should. I am missing
something simple, or is this the best that can be done?

Answer

A:
I've encountered the same problem you describe. I've observed that, while
Delphi scales the StringGrid's font properly, it doesn't change the
StringGrid's "DefaultColWidth" and "DefaultRowHeight" properties.  Thus the
StringGrid itself does not actually change size.

I've worked around this by changing these properties myself:

  with StringGrid1 do begin
      DefaultColWidth  := DefaultColWidth  * MyScaleFactor;
      DefaultRowHeight := DefaultRowHeight * MyScaleFactor;
  end;

Aligning cells in stringgrid

Question

I need to allign cells in a stringgrid, first cell to the left ,second to the
right and so on ...

Answer

A:
Here is some code that should do what you want.

procedure WriteText(ACanvas: TCanvas; const ARect: TRect; DX, DY: Integer;
                    const Text: string; Format: Word);
var
  S: array[0..255] of Char;
  B, R: TRect;

begin
  with ACanvas, ARect do
  begin
     case Format of
        DT_LEFT   : ExtTextOut(Handle, Left + DX, Top + DY, ETO_OPAQUE or ETO_CLIPPED,
                               @ARect, StrPCopy(S, Text), Length(Text), nil);

        DT_RIGHT  : ExtTextOut(Handle, Right - TextWidth(Text) - 3, Top + DY,
                               ETO_OPAQUE or ETO_CLIPPED, @ARect, StrPCopy(S, Text),
                               Length(Text), nil);

        DT_CENTER : ExtTextOut(Handle, Left + (Right - Left - TextWidth(Text)) div 2,
                               Top + DY, ETO_OPAQUE or ETO_CLIPPED, @ARect,
                               StrPCopy(S, Text), Length(Text), nil);
     end;
  end;
end;


procedure TBEFStringGrid.DrawCell(Col, Row: Longint; Rect: TRect; State: TGridDrawState);
var

   procedure Display(const S: string; Alignment: TAlignment);
   const
      Formats: array[TAlignment] of Word = (DT_LEFT, DT_RIGHT, DT_CENTER);
   begin
      WriteText(Canvas, Rect, 2, 2, S, Formats[Alignment]);
   end;

begin
   { test the Col and Row arguments here and format the cells as you want }

   case Row of
   0     : { Center column headings }
           if (Col < ColCount) then
              Display(Cells[Col,Row], taCenter)
   else
      { Right justify all other entries }
      Display(Cells[Col,Row], taRight);
   end;

end;

Coloured StringGrid

Question

Presently working on a project which has to display differently colored cells
in a grid. Nothing fancy like formulas and printing, just displaying
time-related activities in hour or half-hour cells and color-coding these
activities on a per-cell-basis. I must also be able to read back color codes
by clicking in a cell. The grid must allow multiple area selections.

Answer

A:
You can try using a StringGrid. It has an Objects property that you
can assign objects. Create an object which contains a TColor variable
and assign it to Objects[col,row], so you can retrieve it any time. You
must assign an OnDrawCell Event to the StringGrid, drawing the text on
the Correct color. To retrieve the selection, you can use Selection
property, that contains the user Selection. This should be something like that:
    type
    TStrColor = class(TObject)
    public
      Color : TColor;  {you could also define a private variable and 
                        public access methods}
    end;
    ...
    procedure TForm1.FormCreate(Sender:TObject)
    var
      i,j : Integer;
    begin
      With StringList1 do
        for i := 0 to ColCount-1
          for j := 0 to RowCount-1
            Objects[i,j] := TStrColor.Create;
    end;
    ...
    procedure TForm1.StringGrid1DrawCell(Sender: TObject; Col, Row: Longint;
      Rect: TRect; State: TGridDrawState);
    var
     OldColor : TColor;
    begin
      with StringGrid1.Canvas do begin
        OldColor := Font.Color;
        Font.Color := (StringGrid1.Objects[col,row] as TStrColor).Color;
        TextOut(Rect.Left+2,Rect.Top+2,StringGrid1.Cells[Col,Row]);
        Font.Color := OldColor;
      end;
    end;
    ...
    procedure TForm1.ProcessSelection(Sender: TObject);
    var
      i,j : Integer;
    begin
      With StringGrid1.Selection do
        For i := left to right do
          for j := top to bottom do
            MessageDlg(IntToStr(i)+','+IntToStr(j)+'-'+
              IntToStr((StringGrid1.Objects[i,j] as TStrColor).Color),
              mtInformation,[mbOk],0);
    end;
    This component cannot allow multiple selections....

Testing for the existence of a Component Property

Question

I would like to iterate through all my components on a form, at form
activation time, and if a particular component has a Font property, I would
like to set the Font.Pitch value at that time. In general, how do I test to
see if a component has a particular property?

Answer

{you'll probably want to replace the "is TButton.." with some kind of set
that you define on your own.. then you just ask.. if Components[i] in myset
then}

{gee i dont know ... i just looked up TFont and it applies to 40 some
different objects, and everything that has a TFont also has a TPitch ...
there must be an easier way}

procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
begin
  for i := 0 to ComponentCount -1 do
     if Components[i] is TButton then
       TButton(Components[I]).Font.Pitch :=fpFixed ;

end;

Set event-handler at run-time

Question

I need to set the onTimer property of a TTimer-component at run-time.
Lets say I have a procedure called ThisOne and I , want it to be the
eventhandler - how is that done?

Answer

A:
It's pretty straightforward: you define the procedure (say ThisOne) that
takes the same parameter list as onTimer, then when you want it to be
attached to the timer you say:

   Timer1.onTimer := ThisOne;

To turn off the event, use:

   Timer1.onTimer := nil;

The procedure (ThisOne) does not need to be in the same class as the timer,
but it _must_ take the same parameters in the same order or you risk things
going off into la-la land.

A:
The way I'd do it is to put the component on the form and create the
appropriate event. (this gets better - honest). Then copy the procedure line
of the event, rename it and put it in the public section. This ensures that
the event has the right parameters - with some of the longer event lines,
typing the correct definition can be a real hassle.

Remove the component from the form if you don't want it. Put the code you
want called in the new event handler you've defined. When you create the
object put the address of the code into the ONevent handler of the object.

I think the appropriate line of code is:

Timer1.ontimerevent:=formdynamictimerevent;

Application events

Question

I am trying to allow my program to accept file manager drops...I have it
working OK with The Main window opened, but not minimised. The Accept files
works OK, but I am not sure how to get the WM_Drop message. I reallize it must
be somewhere in the OnMessage handler (I know how to create event Handlers for
Application events), but, in the OnMessage handler, how do I get the message?
Msg is NOT one of the parameters.

Answer

A:
The problem here is that when your Delphi application is minimized, it is a
different window handle than at other times. The Application object actually
has its own window handle! Application.Handle is the window that is active when
your app is minimized. When you minimize the app, all the forms are actually
just hidden. Notice the Application methods Minimize and Restore. Also notice
that there are two undocumented events in TApplication, OnMinimize and
OnRestore. These are there because there is no event handler in TForm that will
fire when you minimize the main window. Kind of bizarre. I think it is done
this way to support SDI applications.

ReleaseDC and TCanvas

Question

I have a canvas that I create, and set the Handle using GetWIndowDC.
I do explicitly destroy the Canvas, is is automatically calling RealeseDC?
Is this a safe method of doing this?

Answer

A:
TCanvas will not automatically call ReleaseDC. To create a canvas with
a WindowDC as its handle, probably the best idea would be to create a
descendant of TCanvas (modeled on TControlCanvas):

  type
    TWindowCanvas = class(TCanvas)
    private
      FWinControl: TWinControl;
      FDeviceContext: HDC;
      procedure SetWinControl(AControl: TWinControl);
    protected
      procedure CreateHandle; override;
    public
      destructor Destroy; override;
      procedure FreeHandle;
      property WinControl: TWinControl read FWinControl write =
SetWinControl;
    end;

  implementation

  destructor TWindowCanvas.Destroy;
  begin
    FreeHandle;
    inherited Destroy;
  end;

  procedure TWindowCanvas.CreateHandle;
  begin
    if FControl = nil then inherited CreateHandle else
    begin
      if FDeviceContext = 0 then
        FDeviceContext := GetWindowDC(WinControl.Handle);
      Handle := FDeviceContext;
    end;
  end;

  procedure TControlCanvas.FreeHandle;
  begin
    if FDeviceContext <> 0 then
    begin
      Handle := 0;
      ReleaseDC(WinControl.Handle, FDeviceContext);
      FDeviceContext := 0;
    end;
  end;

  procedure TControlCanvas.SetWinControl(AControl: TWinControl);
  begin
    if FWinControl <> AControl then
    begin
      FreeHandle;
      FWinControl := AControl;
    end;
  end;

Obviously, you should be careful to destroy the TWindowCanvas (or free
its handle) before destroying the control associated with it. Also,
note that the DeviceContext handle will not be released automatically
after completing the processing of each message (as happens with the
handles of TControlCanvas); you must explicitly call FreeHandle (or
destroy the Canvas) to release the handle.  Finally, note that
"WindowCanvas.Handle:= 0" will not release the handle, you must call
FreeHandle to release it.

Using TStringList in a visual component

Question

I need to build a buffer for queuing output requests to be handled by a
component I am trying to build. I thought I would just use a TStringList,
add to it's end and using and deleting the first entry when possible... It
doesn't seem to be this simple... does anyone know the difference between
TStrings and TStringList and which I should be using.... any tips????

Answer

A:
TStrings is an abstract base class used by many of the visual controls such
as TListBox. What you want is TStringList, or if all you need to keep track
of is objects, use TList instead. To add to the end of either, use the Add
method. To insert at a specific place in the list, use Insert. To get a
string from the list, use the Items property. Keep in mind that it is
zero-based, so the last item in the list is referenced by Count-1 (Count is
another property). To delete a string, use the Delete method. To find a
string in the list, use IndexOf. TStringList can be made to keep the list in
alphabetical order. To do that, set the Sorted property to true before you
add anything to it. It can also be used to keep an object for every string,
using AddObject and the Objects property. TList does most of the above, just
without the strings. To create a TStringlist, do this:

procedure MakeList;
var
  aList: TStringList;
begin
  aList := TStringList.Create;
  aList.Sorted := true;  {optional}
  aList.Duplicates := dupIgnore;  {or dupAccept or dupError}
  aList.Add('String 1');
  aList.Add('String 2');
  Edit1.Text := aList.Items[0];   {Edit1 now says 'String 1'}
  aList.Delete(0);
  aList.Free;
end;

#0 KeyPress

Question

I've seen this code in a component I've downloaded:

   if Key <> #0 then inherited KeyPress(Key);

Since Windows virtual keycodes range from 1 to 145 (decimal),
what does 'if Key <> #0' check for?

Answer

A:
It checks for whether a key was really pressed; by convention, #0 means a
key was not pressed. In some cases an event may get activated by other than
a keypress (eg. by direct call), or an ancestor may have already processed
the keypress, in which case Key will have been set to #0.

StringGrid Masks

Question

I have a column in a string grid that I need to be displayed like the
"comma" format of any Spreadsheet:
User types: 123456.7
Grid displays: 123,456.70

Answer

A:
What you want is tricky, but doable cause I've got a right aligned, floating
point, editable grid working right now (albeit a DrawGrid, but the concepts
should be the same).

You'll need a couple of things:
- An OnDrawCell handler to display the formatted, right-justified data.
- The cells of your stringgrid loaded with unformatted string representations of
  your data.

In your OnDrawCell handler you will need to do something like this.  This
code displays right justified comma-tized values:

  begin
  if (Row > 0) and (Col > 0) and (grdDivBudget.Cells[Col, Row] <> '') then
    begin
      {Format floating point string}
      strText := FloatToStrF(StrToFloat(grdDivBudget.Cells[Col, Row]),
ffNumber,         13,2);

      {Set font}
      grdDivBudget.Canvas.Font.Name := 'Courier';
      if StrToFloat(grdDivBudget.Cells[Col, Row]) < 0 then
        grdDivBudget.Canvas.Font.Color := clRed;
      grdDivBudget.Canvas.Font.Style := grdDivBudget.Canvas.Font.Style -
[fsBold];

      {Center the text in the cell from top to bottom, right justify it and
       move the right margin to the left two pixels.}
      X := Rect.Right - grdDivBudget.Canvas.TextWidth(strText);
      Y := Rect.Top + ((Rect.Bottom - Rect.Top -
        grdDivBudget.Canvas.TextHeight(strText)) div 2);
      Dec(Rect.Right, 2);

      grdDivBudget.Canvas.TextRect(Rect, X, Y, strText);
    end;

   Make sure you have DefaultDrawing := True so you only have to write code to
draw the cells which need to be specially formatted.

  That should do it.  When the user tries to edit the number in a cell it
should be displayed in its non-formatted state.  If not the you'll need an
OnGetEditText handler.

  Also, you'll find that validation of the data can be problematic (i.e.
what if the user enters 'TX' in a numeric column).  It's made worse by the
fact that the grid (well, the DrawGrid anyway) does not exhibit consistent
behavior for different ways of navigating the grid (i.e. it triggers
different events if you use the arrow keys, versus the mouse, versus simply
hitting enter).  If this becomes a problem post another message and I'll
give you my solution (which I'm not proud of, but it works).

StringGrid right alignment

Question

To right align the cells in a string grid, and have the numeric cells
containing comma, I did the following.

- Made all the contents of the cells the same length.  Padded with spaces
to ensure all were the same.
- This will enable you to right-align the cells.  Just make sure you use
a TT font else certain letters such as the letter i will take less space
than a letter such as M. I used Courier New as the grids font.

Answer

A:
I suppose this one is better method

procedure TForm1.GridSumaDrawCell(Sender: TObject; ACol, ARow: Longint;
  ARect: TRect; State: TGridDrawState);
var
   dx : integer;
begin
   with (Sender as TStringGrid).Canvas do begin
      Font := GridSuma.Font;
      Pen.Color := clBlack;
      if (ACol = 0) or (ARow = 0) then begin
{ Draw header }
          Brush.Color := clBtnFace;
          FillRect(ARect);
          TextOut(ARect.Left, ARect.Top, GridSuma.Cells[ACol, ARow])
      end
      else begin
{ Draw right aligned cell }
          Brush.Color := clWhite;
          FillRect(ARect);
          dx := TextWidth (GridSuma.Cells[ACol, ARow]) + 2;
          TextOut(ARect.Right - dx, ARect.Top, GridSuma.Cells[ACol, ARow])
      end
     end
end;

FileName property in non-visual component

Question

I'm trying to get a new non-visual component created, and one of the properties
will be a FileName property. How can I set it up so that at design time the
user will get the ellipses button on the property manager and get the file
open box if they click on it and then be able to select a file name that way?

Answer

A:
The following code is taken out of dsgnintf.pas (a file
worth exploring!) for the TMPLayer.filename
property, with help for C.Calvert..

In the component unit file header...

   TFileNameProperty = class (TStringProperty)
     public
      function getattributes: TPropertyattributes; override;
      procedure Edit; override;
    end;

add to the register function...
     RegisterPropertyEditor(Typeinfo(String),
       TMyComponent, 'Filename', TFileNameProperty);

 and the code...

function TFileNameProperty.GetAttributes;
begin
  Result := [paDialog];
end;

Procedure TFilenameProperty.edit;
var
  MFileOpen: TOpenDialog;
begin
  MFileOpen := TOpenDialog.Create(Application);
  MFileOpen.Filename := GetValue;
  MFileOpen.Filter := 'The Right Kind Of Files|*.*'; (* Put your own filter
here...*)
  MFileOpen.Options := MFileOpen.Options + [ofPathMustExist,ofFileMustExist];
  try
    if MFileOpen.Execute then SetValue(MFileOpen.Filename);
  finally
    MFileOpen.Free;
  end;
end;

SendMessage and TLabel

Question

How does one send a message to TLabel particularly since it's a non-windowed
control. I need to send a label WM_LButtonUp, but there's no handle.

Answer

A:
You can't send a message to a control that doesn't have an hWnd (i.e., a
Handle property in Delphi). One solution would be to send the message to
an invisible control that *does* have a handle, and do your Tlabel work
in a handler  for that control.

Call the Hint method directly

Question

Is it possible to call the  Hint method directly?
I've a case where I want to click on a Button, and the Hint for
another component (eg, an edit box) will appear for 1 second or
so, and then it will disappear after the button is released. I
saw something like "ActivateHint" but it seemed that I
couldn't make a direct call.

Answer

A:
 function RevealHint (Control: TControl): THintWindow;
{----------------------------------------------------------------}
{ Pops up Hint window for the specified Control, and returns a   }
{ reference to the hint object so it may subsequently be removed }
{ with RemoveHint (see below).                                   }
{----------------------------------------------------------------}
 var
   ShortHint: string;
   AShortHint: array[0..255] of Char;
   HintPos: TPoint;
   HintBox: TRect;
 begin
   { Create the window: }
   Result := THintWindow.Create(Control);

   { Get first half of hint up to '|': }
   ShortHint := GetShortHint(Control.Hint);

   { Calculate Hint Window position & size: }
   HintPos := Control.ClientOrigin;
   Inc(HintPos.Y, Control.Height + 6);    <<<< See note below
   HintBox := Bounds(0, 0, Screen.Width, 0);
   DrawText(Result.Canvas.Handle,
       StrPCopy(AShortHint, ShortHint), -1, HintBox,
       DT_CALCRECT or DT_LEFT or DT_WORDBREAK or DT_NOPREFIX);
   OffsetRect(HintBox, HintPos.X, HintPos.Y);
   Inc(HintBox.Right, 6);
   Inc(HintBox.Bottom, 2);

   { Now show the window: }
   Result.ActivateHint(HintBox, ShortHint);
 end; {RevealHint}

 procedure RemoveHint (var Hint: THintWindow);
{----------------------------------------------------------------}
{ Releases the window handle of a Hint previously popped up with }
{ RevealHint.                                                    }
{----------------------------------------------------------------}
 begin
   Hint.ReleaseHandle;
   Hint.Free;
   Hint := nil;
 end; {RemoveHint}

The line marked <<<< above is the one that positions the hint
window below the control.  This could obviously be altered if
you want a different position for some reason.

Changing in TOpenDialog

Question

I need to change caption in the check-box "Read Only" of a
TSaveDialog/TOpenDialog. I wish it becomes "Only selected records". Is it
possible? Or I have to create my own component?

Answer

A:
Try Searching for Open Dialog Box in the Windows API help file.  Look down
at the bottom of the topic at lpTemplateName.  Basically, you can create a
new dialog box for the Open Dialog Box and replace the standard one with
your own.

Edit in StringGrid

Question

An annoying problem with StringGrid:
I'm using a stringgrid to display some data, and i start the stringgrid with
GoEditing set to false (The user can't edit the data in the stringgrid),
and selectrow to true (The user can select only whole row).
I placed an "EDIT" button so when the user presses it, i set the GoEditing to
true & selectrow to false and now the user can edit the data in the table.
All is well up to now.
The problem:
When the user pressed EDIT, and edits a cell, then presses EDIT again to
switch back to normal row selecting mode, the cell that was last edited, stays
in an inverted color, meaning , if the focused cell is blue and the rest is
white, then after the user edits a cell and presses EDIT again , the whole line
is blue EXCEPT the cell that was last edited, which remains white.
I should add that the stringgrid i'm using is a modified stringgrid which each
col. can be alligned diffrently (first col. can be rightjustified, second left
justified, third centered justified and so on...) but i don't think this has
anything to do with my problem.

Answer

A:
Haven't tried these, but two possibilities come to mind:

1) On the second edit press, change focus to another field (eg. x.focus
where x is not the grid), reset goEditing and selectRow, then change
focus back to the grid.  (This technique has worked for me in several
places, eg. grids, memos.)

2) On the second Edit press, after resetting goEditing and selectRow,
try creating a tGridRect spanning the row you want to highlight, the
doing grid.Selection := gridRect;

How to detect a row focus change in TDBGrid

Question

There is (very surprisingly) no event triggered when the user changes the
focus from one row to another in a TDBGrid. Am I missing something here? Has
anyone got an easy way?

Answer

A:
You use the OnDataChange event of the Datasource to which the DBGrid is attached.
If the State in the event is dsBrowse then you've gone to a new row (or just
opened the table).

Why not have an event in the grid?  Because the grid may not be the only control
displaying data from the current row and might not be the only way to move from
row to row.  Using the Datasource give centralized event handling.

As to your question about a single click, not sure what you're trying to do, but
you can use TDatasource.OnDataChange to capture row changes and
TDBGrid.OnColEnter/Exit to capture column changes.

A:
The following works for me:

1. To detect row change, use the TDataSource's OnDataChange event.
   OnDataChange occurs whenever scrolling or clicking on a different row
   happens.  The event handler is something like this:

     procedure Form1.DSrc1DataChange(Sender: TObject; Field: TField);

   where Field is the column in which change occured.

   The TTable's fields can be used for comparing the currently selected
   row's fields (key) with whatever your requirement is. The TDBGrid's
   Fields property can also be used the same way. For instance:

     if tbl1.Fields[0].AsString = 'BlaBlaBla' then ...
   or, if dbGrid1.Fields[I].IsNull then ...

2. For column change, use TDBGrid's OnColExit & OnColEnter. The
   TDBGrid's properties SelectedField and SelectedIndex can be used to
   determine the currently selected column.

   When a different column on a different row is selected, you get
   OnColExit, OnColEnter, and then OnDataChange.

3. You can also do some fancy stuff by using the TDBGrid's
   OnDrawDataCell event which occurs when a cell is selected or
   when the grid is scrolled. The event handler looks like:

     procedure Form1.dbGrid1DrawDataCell(Sender: TObject; Rect: TRect;
               Field: TField; State: TGridDrawState);

   But of course you get a lot of draw events when cell changes, and
   you have to do your own filtering.

4. If you don't have a problem in creating "101 variations" on the
   standard components - which I don't 8-) then try this. It is
   easier.

   To access row or column index of the selected cell, you can
   derive a class from TCustomGrid and publish its Row, Col
   run-time properties (current grid row and column, not table's!!):

     type
       TSampleDBGrid = class(TCustomGrid)
       public
         property Col;
         property Row;
       end;

   in some procedure or event handler, do a typecasting:

     var
       G: TSampleDBGrid;
     begin
       G := TSampleDBGrid(myDBGrid1);
       if G.Row = I then ...
       if G.Col = J then ...
       
   This is because TDBGrid is a descendant of TCustomGrid, which
   has several properties on the grid coordinates, but aren't
   published in TDBGrid.

A:
From what I can see, you have to do it programmatically.  OTTOMH,
assuming the grids already exist and you have access to the
underlying ttable::

   grid.colcount := dbGrid.fieldcount;
   table.first;
   row := 0;
   while not table.eof do begin
      grid.rowcount := row + 1;
      for i := 0 to grid.colcount-1 do
          grid.cells[i,row] := dbGrid.fields[i].asString;
      table.next;
      inc (row);
   end;

May be some latent bugs in this, but it should do the trick.

A:
Look at the following code and see if it will help. It takes the 'Name'
property of a control and then places it into the 'Caption' property of a
label.

unit Unit1;

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Label1: TLabel;
    Edit1: TEdit;
    Edit2: TEdit;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Edit1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure Edit2MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
begin
  close;
end;

procedure TForm1.Edit1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  Label1.Caption := TEdit(Sender).Name;
end;

procedure TForm1.Edit2MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  Label1.Caption := TEdit(Sender).Name;
end;

end.

Combobox problems

Question

I have a combo box. On the On Change event I have some processing, which if
OK will update the results, and the combo box selection is valid. However,
if the processing fails, the combo box selection should revert to the
previous value. How do I make it do this?

Answer

Try saving the Index value in a variable in the OnEnter method or the OnCreate
method of the form.  Then, to reject the user's selection, simply
  ComboBox1.ItemIndex := var1;

Change Grid Cell Color

Question

I have a dbeGird wich displays a lot of numbers. How can I change the color
of the cells which has a value below zero.

Answer

A:
Attach the following code to the DBGrid's OnDrawCell event handler:

procedure TForm1.DBGridDrawDataCell(....);
begin
  if Table1.FieldByName( 'SomeField').AsFloat < 0 then
      DBGrid1.Canvas.Font.Color := clRed
      else DBGrid1.Canvas.Font.Color := clBlack;
  DBGrid1.DefaultDrawDataCell( Rect, Field, State );
end;

[Patrick Allen, patrick@blueridge.com]

A:
In the dbgOrdRegDrawDataCell put the next lines.

if ((Field.FieldName = 'CalcAmout') and (tbOrdCalcAmount.AsFloat < 0)) then
   dbgOrdReg.Canvas.Font.Color := clRed
dbgOrdReg.DefaultDrawDataCell(Rect,Field,State);

This is working at my place and if you make a if .. else if ... else if..
you can test every grid cell and change the colors.

[Rene Groothuis, steelcover@dataweb.nl]

A:
Well I've found the solution. It's like this:

In the dbgOrdRegDrawDataCell put the next lines.

if ((Field.FieldName = 'CalcAmout') and (tbOrdCalcAmount.AsFloat < 0)) then
   dbgOrdReg.Canvas.Font.Color := clRed
dbgOrdReg.DefaultDrawDataCell(Rect,Field,State);

This is working at my place and if you make a if .. else if ... else if..
you can test every grid cell and change the colors.

TEdit and OnEnter event

Question

I would like to write a TEdit component, where on the OnEnter event,
it will display a hint in a specified label.  My question is how do
override the OnEnter event in my component to do the extra bit that I
want.

Answer

A:
interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls;

type
  TNewComponent = class(TEdit)
  private
    { Private declarations }
  protected
    { Protected declarations }
    procedure DoEnter; OverRide;
  public
    { Public declarations }
    constructor Create(AOwner:  TComponent); OverRide;
    destructor Destroy; Override;
  published
    { Published declarations }
  end;

procedure Register;

implementation

procedure TNewComponent.DoEnter;
begin
  inherited DoEnter;
  
end;


destructor TNewComponent.Destroy;
begin
  inherited destroy;
end;


constructor TNewComponent.Create(AOwner:  TComponent);
begin
  inherited Create(AOwner);

end;

procedure Register;
begin
  RegisterComponents('Samples', [TNewComponent]);
end;

end.

How do I create a component like TField

Question

I'd like to create component (subclass of TComponent) which will be
not visible on a form at design time just like subclasses of TField.
More precisely I want to have to non-window component types: first
called TListComponent and the second TListElement. Now I can write
the component editor for TListComponent and the editor can create
a plenty of TListElement components. The problem is:

  1. If I do not RegisterComponent for TListElement then creating
     it's instance at design time raises GPF.

  2. If I do RegisterComponent for TListElement then creating the
       instance of TListElement creates an icon placed on the form and
      I do not want it. I prefer to keep TListElement invisible at design time
      just like TTable does with TField.

Answer

A:
Have you considered not making it a component, but a class?  A class is
programmatic, not part of a form.  If you put the class in a unit (say
myclass.pas) and then in your program put "uses myclass;", then you can just
use it as a class, eg.

 type
    aninstance: tMyclass;
 begin
    new (aninstance);
        {equivalent to aninstance := tMyclass.create; }
    ...
      { use aninstance here }
    ...
    dispose (aninstance);
       { equivalent to aninstance.free; }
 end;

Accessing notebook pages

Question

I want to add components at runtime to a page of a notebook.
How do I do it so that when I change the page the components disapear and
reapear.

Answer

A:
When you add the components at runtime, you need to set each component's
parent to the desired _page_ of the notebook, not to the notebook itself.

You can do this the following way (this example is for a Button):

    MyButton := TButton.Create( Form1 );  {as usual...}
    ...
    ...
    MyButton.Parent := TTabPage( TabbedNotebook1.Pages.Objects[n] );
      { <== where 'n' is an index into the desired page ==> }

The notebook's 'Pages' property is a StringList containing a list of
captions and 'TTabPage' objects.

I used this technique a few months back myself.  I can't remember now where
I picked this information up.  I can't find documentation for it at the
moment.  Maybe someone else knows where this is documented?

A:
To add a component to a TabbedNotebook page at run-time a pointer to the
desired page must be assigned to the new component's Parent property before it
can be shown. The way to access all the pages of a TTabbedNotebook at run-time
is with the Objects array property of the TabbedNotebook's Pages property.
In other words, the page components are stored as objects attached to the page
names in the Pages string list property. The follow demonstrates the creation
of a button on the second page of TabbedNotebook1:

   var
     NewButton : TButton;
   begin
     NewButton := TButton.Create(Self);
     NewButton.Parent := TWinControl(TabbedNotebook1.Pages.Objects[1])
     ...

   This is how a TNotebook page would be used as a parent to a newly
   created component on that page:

   NewButton.Parent := TWinControl(Notebook1.Pages.Objects[1])

   This is how a TTabSet tab page would be used as a parent to a
   newly created component on that tab page:

   NewButton.Parent := TWinControl(TabSet1.Tabs.Objects[1])

Change delete behavior in Memo

Question

I need to change the behavior of the delete key in a memo
box.  The new behavior will stop the CR/LF from being deleted if delete is
pressed at the end of a line, or, backspace is pressed from the beginning of a
line. I want each character pressed to remain on the line it was intitially
input. There will always be 6 lines in the memo, and line 3 cannot be moved to
line 2. I still would like to be able to delete characters, just not the
CR/LF.

Answer

A:
Just change the Memo's OnKeyDown event handler to look like:-
  if Key = VK_DELETE then
  begin
    do whatever you want in here
  end;
  if Key = VK_BACK then
  begin
    do whatever
  end;
Probably better to use a CASE here but I'm not sure if CASE allows VK_?? in it.
May also need to include the Inherited for the keys you don't handle. Anyone
want to
clarify this ?
Also look up the SelStart to determine where you are in the line like so:-
  var
    Lpos, Cpos : Integer;
  Lpos := SendMessage(memo1.Handle,EM_LINEFROMCHAR,Memo1.SelStart,0);
  Cpos := SendMessage(memo1.Handle,EM_LINEINDEX,Lpos,0);
  CPos := Memo1.SelStart-CPos;

A:
since VK_? stuff are integers this will work :

case Key of
  VK_DELETE :
    begin
      Key := 0;  {this stops the keydown message from going any
                         farther, for ex. the form and its components}
      stuff to do;
    end;
  VK_BACK:
    begin
      Key := 0;  {this stops the keydown message from going any
                         farther, for ex. the form and its components}
      stuff to do;
    end;
  end;

Listbox with graphic

Question

Has anyone done something like putting a .BMP graphic besides text within a
listbox?  something you see when you click on "Options"-"Environment"->
"Palette".

Answer

A:
This is a sample code. You need to set the ListBox property
Style to lbOwnerDrawFixed. Then you draw the bitmap on the event
DrawItem (see OwnerDraw at the Delphi Help File).

unit Listemas;

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls;

type
  TLTemas = class(TForm)
    ListBox1: TListBox;
    procedure FormActivate(Sender: TObject);
    procedure ListBox1DrawItem(Control: TWinControl; Index: Integer;
      Rect: TRect; State: TOwnerDrawState);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  LTemas: TLTemas;

implementation

{$R *.DFM}

procedure TLTemas.FormActivate(Sender: TObject);
var
   Dibujo: TIcon;
begin
     with ListBox1.Items do
     begin
          Dibujo := TIcon.create;
          Dibujo.LoadFromFile('.\ICO\justic.ico');
          AddObject('Poder Legislativo y Partidos Politicos',Dibujo);
          Dibujo := TIcon.create;
          Dibujo.LoadFromFile('.\ICO\justic.ico');
          AddObject('Poder Ejecutivo Nacional',Dibujo);
     end;
end;

procedure TLTemas.ListBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
var
  Icon: TIcon;
  Offset: Integer;	{ text offset width }
begin
  with (Control as TListBox).Canvas do	{ draw on the control canvas, not on the form }
  begin
    FillRect(Rect);	{ clear the rectangle }
    Offset := 2;	{ provide default offset }
    Icon := TIcon((Control as TListBox).Items.Objects[Index]);	{ get the Icon for this item }
    if Icon <> nil then
    begin
         Draw(Rect.Left+1,Rect.Top+2,TIcon((Control as ListBox).Items.Objects[Index]));

      Offset := Icon.width + 9;	{ add four pixels between Icon and text }
    end;
    TextOut(Rect.Left + Offset, Rect.Top+7, (Control as TListBox).Items[Index])	{ display the text }
  end;
end;

end.

A:
Check out the OnDrawItem event for the listbox (or combobox etc).
There you will see that drawing graphics is as easy as writing text.
(Once you are comfortable with that check out OnMeasureItem event)

procedure ListDrawItem(Control: TWinControl; Index:
Integer;
  Rect: TRect; State: TOwnerDrawState);
var
 BitMap : TBitMap;
begin
{Initialize Bitmap here.... i.e. load a graphic onto it}

     With (Control as TListBox).Canvas do
       begin
         FillRect(Rect);
         Draw(Rect.Left, Rect.Top, BitMap);
         TextOut(Rect.Left + 2 + BitMap.Width, Rect.Top,
         DstList.items.strings[index]);  {DstList is the name of the 
list}
       end;

end;

Masked Find in TStringList

Question

I am trying to do a masked find or search in a TStringList Object. Something
like MyList.Find('LeftPelvic*') to find all strings that start with LeftPelvic
and have it return a TStringList or find the first and have a findnext method.

Answer

A:
   list : tStringList;   { the tStringList to search }
   target: string;       { the string to search for, eg. 'LeftPelvic' }

   { set start to where you want to start from, eg. 0 to start at the top
     or some integer to start within the list, eg. the last value of i + 1 }
   { following is case insensitive, to make it case sensitive remove the calls
     to ansiUpperCase }

   target := ansiUpperCase (target);
   for i := start to list.count-1 do
      if ansiUpperCase(copy(list.item[i], 1, length(target))) = target then
         found it!

If you want to do a looser search, eg. find 'LeftPelvic' anywhere in the
list item rather than just at the beginning, change the if statement to:

   if pos (target, ansiUpperCase(list.item[i])) > 0 then found it;

Dual list box

Question

Could anyone give me some hints on how to create a dual list box (add
and remove items betweem 2 listboxes) with the items of the src list come
from a table.
I have tried the dual list box template, but it is a listbox with
pre-entered items. I want to hv the items from a table instead. Which one
(ListBox, DBListBox or ...) should I use?

Answer

A:
I placed two listboxes on the screen and used the following code to fill one
of them
with data from a table.

tableName.Refresh;  {this may or may not be necessary in your case}
tableName.First;    {Make sure you are looking at the first record}
while not tableName.Eof do {loop through table}
begin
  listbox1.items.add(tableName.FieldByName('USRID').AsString); {add the item
to the listbox1}
  tableName.Next; {go to the next record}
end;

this is an actual procedure from some code that I am using to accomplish the
same thing.
I pass it a tablename and listbox1 name and listbox2 name.  I use several
tables that
have the same type of fields that is why I use this routine:

procedure TTemplateFrm.buildList(tableName: TTable; SelBox, AvailBox: TListBox);
begin
  {we are going to add data to listboxes in this procedure}
  {pick up any new data}
  tableName.Refresh;
  {make sure we are looking at the first record}
  tableName.First;
  {Now clear the list boxes}
  SelBox.Clear;
  AvailBox.Clear;
  {Now add the Stations}
  while not tableName.EOF do
  begin
    AvailBox.Items.Add(tableName.fieldByName('USRID').AsString +
       ' ' + tableName.fieldByName('DESCRIPTION').AsString);
    tableName.Next;
  end;
end;


How do you want to move data between the two boxes? If you want to drag and
drop then
use begindrag in your mousedown event of the source table like this:

  if Button = mbLeft then
    Tlistbox(sender).BeginDrag(false);

Then in your other listbox, the one receiving the data you type in the
follow code in DragOver event:

  if Source = ListBox1 then
    Accept := true
  else
    Accept := false;

Do not use "Accept := (Source is TListbox)" as most examples show, because
you have two list boxes
you need to refer to the name of the object and not the type or you will
confuse your program.

Finally in your dragDrop you can use the following code to add the data to
listbox2 and remove it from list
box1.

  Listbox2.Items.Add(Listbox1.Items[Listbox1.ItemIndex]);
  Listbox1.Items.Delete(Listbox1.ItemIndex);

Finally add a save button if you want to loop through the listbox2 and save
it to a database.

I hope this is what you were looking for and that you find this useful. If
you want to be able to move
data from listbox2 to listbox1 as well then use the same code but REVERSE
which boxes you place them in 
and make sure you reference the correct boxes in the code.

OnClose proc

Question

Just wanted to be sure of something...If an application is shut down, by
any method within the program, it DOES call the OnClose procedure for any
open forms, right?  But if by chance it was shut down by Windows, like a
situation where Task Manager was called and it is shut down from there,
then although the program itself is over, any Forms that might have been
open and were not created in the project source file, could still be lost
in memory, is that right?

Answer

A:
My experience is that OnClose is only called when Close is called.
OnDestroy, however is always called when the form being freed or
destroyed.  I had put my Table.Post stuff in OnClose, but it was not
always called.  Try putting MessageBeep into OnClose, then close your
app a few different ways to see exactly when it is called.


A:
I ran a test that suggested an Application.Terminate will fire the Destroy
event for the form but NOT the OnClose or OnCloseQuery events, unlike VB.


A:
It's my understanding that the Task Manager does not actually close
the application.  It just tells the application to close itself, by
way of sending messages directly to the application.  I would hope
that the default window proc. that Delphi puts into your application
would handle going through the list of child components that your
application has and tells them all to close as well.
You should be able to test this by starting something you've written
in Delphi and having the Task Manager close it.  Do this several
times and see if you free system resources decreases.

Colouring fields in DBGRIDS

Question

I currently have an application using a DBGRID. I want to display a memofield
in the grid in red if the memo has text in it, Default colour if not.
I have tried doing this by setting the default font for the grid to red
if the field about to be drawn is the required one and the memofield is
not null.

Answer

A:
Here's an example that uses the demo COUNTRY table and draws
the text of each row in red where the population is less than 10 million.
Hope it helps.

Respond to an OnDrawDataCell event.

   begin
     if Table1.FieldByName('Population').AsFloat < 10000000 then
        DBGrid1.Canvas.Font.Color := clRed;
     dbGrid1.DefaultDrawDataCell(Rect,Field,State);
   end;

"Autosizing" StringGrids

Question

Is there a way to make my Stringgrids "Autosize columns? I
am loading the stringgrid from static code and would like to
hae the column widths autosize to fit the text loaded.

Answer

A:
Not really, but OTTOMH you can do it programmatically like this (do this
after you've loaded the data, or if you're loading the data in columns you
can do it within this loop too):

   i,j,temp,max: integer;

   for i := 0 to grid.colcount-1 do begin
      max := 0;
      for j := 0 to grid.rowcount-1 do begin
         temp := grid.canvas.textWidth (grid.cells[i,j]);
         if temp > max then max := temp;
      end;
      grid.colWidths[i] := max + grid.gridLineWidth +1;
   end;

You may need to fudge with the +1 to give each column a little border space.

Right aligning menus

Question

How do I get a menu entry to right align in a window's menu bar? I want all
but Help left aligned, and then Help right aligned. The Windows API help file
suggests it can be done in a menu resource, but Delphi doesn't seem to
recognise the command.

Answer

A:
    It's not really fashionable any longer to right align the
    Help menu, so I only use the following trick to push a Debug
    menu over to the right.

    The technique is to prefix the menu caption with a Backspace
    i.e. Chr(8).

    I haven't found a way to do it at design-time, so you'll
    probably have to do at run-time (e.g. in the FormCreate
    procedure) with something like:

       MenuName.Caption := Chr(8) + MenuName.Caption;

Which button on panel is the Sender

Question

I've a panel on my application that has about 5 buttons.
A popup menu is attached to the Panel
When I right-click on the panel, how do I know which
button the right click fell on, and triggered the popup?
It seems that the Sender of the PopupMenu is the Panel.
But I need to know which button the click fell on.
I tried to put this in the button's OnMouseDown code but
it didn't work.

procedure TTBar2.ToolBarSpdBtn1MouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Shift = [ssRight] then ShowMessage('Right button!');
end;

Answer

A:
I think you might have to get rid of the PopupMenu property in the panel and
use the mouse down event of the button to do what you did above.  Also, I
think you could try to turn off the Auto property.  Then put this in your
OnMouseDown:

procedure TForm1.BitBtn1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  P: TPoint;
begin
  with Sender as TBitBtn do
    if Button = mbRight then
      begin
        P := Self.ClientToScreen (
           Point (Left + (Width div 2), Top + (Height div 2)));
        PopupMenu1.Popup (P.X,P.Y);
      end;
end;

Publishing properties that are sets

Question

I need to publish a property that is a set such as

 type
   myset =  set of (msThis, msThat, msThese, msEtc);

   myClass = class(TComponent)
     fMySetProperty : myset;
   published
     MySetProperty : myset read fMySetProperty write fMySetProperty;
   end;

the compiler lets me put this guy in the public portion of the
declaration, but not published. Says that it can't be published.

Answer

I think your problem is that any field or method that is not explicitly
put under another kind of protection is assumed to be published, I
suspect what you meant to do was:

myClass = class(TComponent)
private { !! you omitted this protection directive }
  fMySetProperty : myset;
published
  MySetProperty : myset read fMySetProperty write fMySetProperty;
end;

Right Text Alignment in edit box

Question

One of these involved a form with a dbEdit box (TdbEdit) to
display the value of a float field) together with a standard
edit box (TEdit) to display the value of a float variable
(formatted as a text string).
The dbEdit box displayed the numerical value right-aligned,
which was most suitable indeed. I therefore also wanted the
standard edit box to do the same. However, I was unable to
get its text string to align itself in any other way than to
the left.
Can any of you Delphi gurus point me in the right direction
please. (How, for example, does Borland pull this stunt off
for dbEdit).

Answer

TEdit1  = class(TEdit)
  public
    procedure CreateParams(var Params: TCreateParams); Override;
  end;

procedure TEdit1.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.Style := Params.Style or ES_MULTILINE or ES_RIGHT;
end;

Multi line Hints

Question

Is there a way to format the HINT property such that it spans
multiple lines versus a single long line?

Answer

A:
Not in the Object Inspector, but you can do it programmatically:

   edit1.hint := 'This is line 1'#13#10'This is line 2';

You could also do this as a hybrid by making a convention that a certain
character sequence represents a new line and replacing that at form create
time.  Eg., you could have the Hint property for Edit1 set to 'This is
line1::This is line 2' then in your FormCreate you can do:

   while true do begin
      i := pos ('::', edit1.hint);
      if (i = 0) then break;
      edit1.hint[i] := #13;
      edit1.hint[i+1] := #10;
   end;

Note that even if there are multiple lines the limit for a Hint is 255
characters.  You can also make the above a procedure and do it for several
components.

TTimer question

Question

I try to make a scheduler for starting an application at a certain time. To
be more specific, this scheduler should call a certain phone number and
leave a message. So I used a tTimer, set the Interval to a large value
(longint), only to discover that this value always put the phone ringing
after about 1 minute, yes, 65.535 milliseconds.
Is there a way to build a timer that uses a longint value for the interval?
Or does anybody know of an example or a component I can use to reactivate a
program after a longer period of time?

Answer

A:
Interval is defined as a word, so 65,535 is the most you're going to get
into it.  Don't know about other components, but to use the timer for over
one minute try this:

   var
      numOfMinutes: integer;
   ...
   numOfMinutes := 30;
   timer1.interval := 60000;
   timer1.enabled := true;
   ...
   procedure Timer1Timer;
   begin
      dec (numOfMinutes);
      if numOfMinutes = 0 then begin 
         { do whatever needs to be done }
      end;
   end;

A:
unit Alarm;

{
Copyright (c) 1996 Eric Nielsen. All Rights Reserved.

Permission to use, copy, modify, and distribute this software for
NON-COMMERCIAL or COMMERCIAL purposes and without fee is hereby granted.
}

interface

uses
  SysUtils, WinTypes, WinProcs, StdCtrls, Controls, Classes, ExtCtrls, Forms;

type
  TForm1 = class(TForm)
    Timer1: TTimer;
    Button1: TButton;
    Label1: TLabel;
    Label2: TLabel;
    Edit1: TEdit;
    procedure Timer1Timer(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure FormAlarm(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}
type
  TAlarm=class(TComponent)
  private
    FAlarmTime : TDateTime;
    FOnAlarm: TNotifyEvent;
    FTimer : TTimer;
    FEnabled : Boolean;
    procedure SetOnAlarm(Value: TNotifyEvent);
    procedure SetEnabled(Value: Boolean);
    procedure SetAlarm(Value: TDateTime);
    procedure AlarmTimer(Sender: TObject);
  protected
    procedure Alarm; dynamic;
    procedure UpdateAlarm;
  public
    property AlarmTime : TDateTime read FAlarmTime write SetAlarm;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property OnAlarm: TNotifyEvent read FOnAlarm write SetOnAlarm;
    property Enabled: Boolean read FEnabled write SetEnabled default False;
  end;

procedure TAlarm.SetOnAlarm(Value: TNotifyEvent);
begin
  FOnAlarm := Value;
  UpdateAlarm;
end;

procedure TAlarm.SetEnabled(Value : Boolean);
begin
  FEnabled := Value;
  UpdateAlarm;
end;

procedure TAlarm.SetAlarm(Value: TDateTime);
begin
  FAlarmTime := Value;
  UpdateAlarm;
end;

procedure TAlarm.Alarm;
begin
  If Assigned(FOnAlarm) then FOnAlarm(Self);
end;

procedure TAlarm.UpdateAlarm;
begin
  If Assigned(FOnAlarm) and FEnabled
    Then
      FTimer.Enabled := True
    Else
      FTimer.Enabled := False;
end;

constructor TAlarm.Create(AOwner: TComponent);
begin
  FTimer := TTimer.Create(Self);
  With FTimer do
    Begin
      Enabled := False;
      Interval := 250;
      OnTimer := AlarmTimer;
    End;
end;

destructor TAlarm.Destroy;
begin
  FEnabled := False;
  UpdateAlarm;
  FTimer.Free;
end;

procedure TAlarm.AlarmTimer(Sender: TObject);
begin
  If Now >= FAlarmTime
    Then
      Alarm;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Label1.Caption := DateTimeToStr(Now);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Label2.Caption := 'Waiting...';
  With TAlarm.Create(Self) do
    begin
      AlarmTime := StrToDateTime(Edit1.Text);
      OnAlarm := FormAlarm;
      Enabled := True;
    end;
end;

procedure TForm1.FormAlarm(Sender: TObject);
begin
  (Sender as TAlarm).Free;
  Label2.Caption := 'BEEP!';
end;


end.

Make TAB act like ENTER in StringGrid

Question

I've tried putting code in the
OnKeyPress event of the Grid, but it does not make the focuseed cell
moved to the next cell as the TAB key does.

Answer

A:
This code moves it to next column.  On reaching end of column, it moves
to a new row.  On reaching very end of grid, it goes back to the very top -
of course, you can modify it to go to the next control.

procedure TForm1.StringGrid1KeyPress(Sender: TObject; var Key: Char);
begin

  if Key = #13 then
    with StringGrid1 do
      if Col < ColCount-1 then {next column!}
        Col := Col + 1
      else if Row < RowCount-1 then begin {next Row!}
        Row := Row + 1;
        Col := 1;
      end else begin {End of Grid! - Go to Top again!}
        Row := 1;
        Col := 1;
        {or you can make it move to another Control}
      end;
end;

Cycle through list of components

Question

procedure TForm1.OnCreate(whatever)
var i : Integer
begin
  for i := 1 to 5 do
  Edit[i].X := 10; {received compiler error here, didn't recognize the Edit
control}
end;

What wrong here??

Answer

A:
procedure TForm1.FormCreate(Sender: TObject);
var
  I : integer;
begin
     for I:= 0 to ComponentCount -1 do
        if (Components[I] IS TEdit) then
           (Components[I] AS TEdit).{Yourparameter} := {your value};
end;


If you need to identify a particular set of edit components, you can put 
them on a panel & use something like

procedure TForm1.FormCreate(Sender: TObject);
var
  I : integer;
begin
     with MyPanel do
       for I:= 0 to ControlCount -1 do
          if (Controls[I] IS TEdit) then
             (Controls[I] AS TEdit).{Yourparameter} := {Your value};
end;

A:
The major one is that Edit1, Edit2 ect is not the same as
Edit[1], Edit[2]. If you want to access a series of controls as an array,
you have to put them into a TList.

MyArr := TList.Create;
MyArr.Add(Edit1);
MyArr.Add(Edit2);
  ...

For i := 0 To MyArr.count - 1 Do
  (MyArr.items[i] As TEdit).Enabled := False;

MyArr.Free;

A:
procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  for I := 0 to ComponentCount -1 do
     if Components[I] is TEdit then
       TEdit(Components[I]).Whatever := 10;
end;

A:
To access them just use:
TButton(mylist.items[i]).property := sumpin;
or
TButton(mylist.items[i]).method;

This is great for doing batch operations on the components or accessing them
in a linear fashion. For doing what you want there is a much easier way of
handling the problem and one that can be done at design time. Set the tag
property and take advantage of the fact that all components are derived from
TComponent.

Procedure TMyForm.MyButtonHandler(Sender: TObject);
Begin
  Case (Sender As TComponent).Tag Of
    1 : { do something }
    2 : { do something else }
     .
     .
  End;
End;

Just point the OnClick event to MyButtonHandler for all the buttons you want
to use this common handler.

Splitter bar

Question

Do you know where I can find "the code for a splitter bar" ?

Answer

A:
I use a TOutline aligned to alLeft, with desktop to the right of it. A
panel is placed after the TOutline, and also aligned alLeft. This makes it
stick to the TOutline. I call the new panel 'splitter'. Make the splitter
very narrow, bevel it if you like, and set it's cursor to east-west.
Replace the TOutLine with what ever type of component you need.
Attach the following to the mouse events:

 procedure TMainForm.SplitterMouseMove(Sender: TObject; Shift: TShiftState;
   X, Y: Integer);
 begin
   if ssLeft in Shift then begin
     outline.Width := outline.Width + X;   {replace outline with your object}
   end;
end;

Popup menu in dependence on mouse position

Question

I need to pop up a popup menu when a user stops highlighting text in a
TMemo..  Problem is i can not figure out who to get the mouse coordinates
with which to call PopUp (i want the menu to pop up at the place the mouse
is release).

Answer

A:
procedure TForm1.Memo1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var ClientPoint,ScreenPoint: TPoint;
begin
 if Memo1.SelLength>0 then
 begin
  ClientPoint.X := X;
  ClientPoint.Y := Y;
  ScreenPoint := ClientToScreen (ClientPoint);
  PopupMenu1.Popup (ScreenPoint.X, ScreenPoint.Y);
 end;
end;

TabbedNotebook and common components on all pages

Question

I want to know how to define some component common to all the
pages on a tabbedNotebook without having to define them on every page
(like the OK and the Cancel Button from the TabbedNotebook template).
I would like to have some labels and entry fields to be available on
all pages.

Answer

A:
The components that you want to appear on all pages have to be owned by
the parent of the TTabbedNotebook (often the TForm on which it appears),
and, apparently, also need to be created AFTER the TTabbedNotebook.  The
simplest way I've found to get this to happen is to place the
TTabbedNotebook, but be sure that you can still get at the parent (i.e.
don't set the .Align property yet), then place the buttons (or whatever)
on the parent area, then set the .Align property for the
TTabbedNotebook, the controls you placed after the TTabbedNotebook,
should then appear on all pages (actually, they exist "on top" of the
TTabbedNotebook as a whole. If you have already placed components,
I've found that using the "Edit/Send to Back" command with the
TTabbedNotebook selected, will bring the desired components on top. You
can also edit the .DFM file directly to make sure the creation order is
what you want.

How to disable a tab(page) in a Notebook component

Question

On my form I have a TTabbed notebook component. However at runtime I want
to be able to disable a page or two on the fly. How do I do that?

Answer

A:
In the OnChange event of your TTabbedNotebook place somethig like this:

if (NewTab = 0) and (IWantToDisableTab0) then
   AllowChange := False;

if (NewTab = 1) and (IWantToDisableTab1) then
   AllowChange := False;

...

Yes, you could use a Case construction, but you's still need an If
for each value.

Insert text in MEMO

Question

Can it be true, that you can't insert a text in a TMemo at the cursor
position?
I wanted to use some buttons to insert standard phrases in a Memo field.
I solved the problem by using TEdit instead of TButton. Then I select the
text, copy it to  the clipboard, and paste from the clipboard to the
Memo.
That's OK, but I don't like to use the clipboard in the program. The user
might have stored something there too.

Answer

A:
Use the Windows API message-EM_REPLACESEL: (from Windows API Help)

EM_REPLACESEL
wParam = 0;                               /* not used, must be zero */
lParam = (LPARAM) (LPCSTR) lpszReplace;   /* address of new string  */

An application sends an EM_REPLACESEL message to replace the current
selection in an edit
control with the text specified by the lpszReplace parameter.

Parameter	Description
lpszReplace	Value of lParam. Points to a null-terminated string containing the
                            replacement text. { Pointer to the string }

Returns
This message does not return a value.

Comments
Use the EM_REPLACESEL message when you want to replace only a portion of the
text in an edit control.
If you want to replace all of the text, use the WM_SETTEXT message.

If there is no current selection, the replacement text is inserted at the
current cursor location.

A:
Make a list with your standard phrases and use the "OnClick" or
the "OnMouseDown" Event in combination with "Alt", "Shift" or
"Ctrl".  Example: When the user press the "Alt" key in combination
with the Right button of your mouse, pop-up the phrases list and
insert the selected phrase in your TMemo component.

A:
to insert a string into a memo :

procedure TForm1.Button1Click(Sender: TObject);
begin
     with Memo1 do begin
      SelStart:=10;
      SelLength:=0;
      SelText:='This is a string inserted into a memo, at 10th char position ';
   end;
end;

to insert a string AND replace some existing text :

procedure TForm1.Button1Click(Sender: TObject);
begin
     with Memo1 do begin
      SelStart:=10;
      SelLength:=20;
      SelText:='This is a string inserted, at 10th char position replacing 20 chars ';
   end;
end;

A:
Put the text you want to insert into a PChar variable, then insert
the text into the memo, using the SetSelTextBuf command, where
SelStart is set to the postion of the cursor in the TMemo.
It works  great ..

Another thing, you can get around the 32K limit on the TMemo
component, if you by-pass the Lines.LoadfromFile method/command. Its
has an inbuilt limit of 32K. If you load the file you want into a
pointer, and using the SetTexBuf command/method, you can load up to
64K of text into a TMemo.

Name of the item in a TListBox

Question

How do I get the name of the item in a TListBox?

Answer

A:
Use the Items and ItemIndex properties:

Foo := Bar.Items[Bar.ItemIndex];

Foo would now contain the selected item in the listbox.

A:
ShowMessage(  Listbox1.Items[Listbox1.ItemIndex] );

Sync'ing Tabset with Listbox

Question

I have a form with a tabset and a listbox.  The tabset has the strings
'A' - 'Z'.  The listbox has a list of name strings Ex. Holmes, Shane A
                                                       Doe, John D.
                                                       Doe, Jane J.

How does one syncronizr the tabset with the listbox.
For example:
   If the letter 'D' was chosen from the tabset, the listbox would
highlight the first item in the listbox beginning with the letter 'D'.

Answer

I've done something similar in the past, however, instead of using a Listbox,
I used a dbGrid with the following options:

[dgAlwaysShowEditor,dgTabs,dgRowSelect,dgAlwaysShowSelection,dgConfirmDelete,
dgCancelOnExit]

In addition, this is what I used when the tab was clicked upon.Thus changing
to the record in the dbgrid.

procedure TForm1.TabSet1Change(Sender: TObject; NewTab: Integer;
  var AllowChange: Boolean);
begin
Table1.FindNearest([Chr(NewTab+65)]);
Table2.FindNearest([Chr(NewTab+65)]);
end;

A:
procedure TForm1.TabSet1Click(Sender: TObject);
var
  I: integer;
begin
  with TabSet1 do
  begin
    if TabIndex > -1 then
    begin
      with ListBox1 do
      begin
        for I := 0 to Items.Count - 1 do
        begin
          if Pos(Tabs[TabIndex], Items[I]) = 1 then
          begin
            ItemIndex := I;
            break;
          end;
        end;
      end;
    end;
  end;
end;

TDBGrid - Vertical Scrollbar

Question

I use the TDBGrid frequently for display of tables. The only real
inconveniance is that slider of the vertical scrollbar in Delphi 1 and
2 only has three positions (top, middle, bottom).

Answer

There are several good reasons for this behavior. First, the program needs
to be consistant. Second, Delphi deals with a number of different DBs,
engines, and sizes of databases. If it were to present a slider which
represented position in the database in a more realistic manner, it would
have to know how many records were in the database. However, because the
access methods differ so much, that information is in some cases difficult
or very slow to retrieve. Thus, I was told, Borland decided not to have that
be the behavior.

If you have source, you may be able to inherit and modify...

Variable control-names

Question

I have a form with (let's say..) 100 labels. Then there is a function
generating a integer value between 1 and 100; The value identicates the
number of the label which caption I want to change. How can I manage this
without writing very long code.

Answer

    Here is a sample code I just made to test dynamic component creation;
    I hope this helps you...:

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    arr : array [1..30] of TBitBtn;
    procedure BitButtonClick(Sender: TObject);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.BitButtonClick(Sender: TObject);
begin
  if TBitBtn(Sender).Font.Style = [] then
  begin
    TBitBtn(Sender).Font.Style := [fsBold];
    TBitBtn(Sender).Font.Color := clRed;
  end
  else
  begin
    TBitBtn(Sender).Font.Style := [];
    TBitBtn(Sender).Font.Color := clBlack;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  x, y,
  f : integer;
begin
  x := 20;
  y := 20;
  for f := 1 to 30 do
  begin
    arr [f] := TBitBtn.Create (Form1);
    if arr [f] <> nil then
    begin
      arr [f].Parent := Form1;
      arr [f].left := x;
      arr [f].top := y;
      arr [f].width := 35;
      arr [f].Height := 25;
      arr [f].caption := IntToStr (f);
      arr [f].tag := f;
      arr [f].OnClick := BitButtonClick;
      inc (x, arr [f].width + 20);
      if (x + arr [f].width) > form1.width then
      begin
        x := 20;
        inc (y, arr [f].height + 20);
      end;
    end;
  end;
end;

end.

A:
There are two ways I can think of:

(1) Using FindComponent. Will have to use the components name:

	var
	   lab: TLabel;
	begin
	     lab := TLabel(FindComponent('Label1'));
	     lab.Caption := 'Hey!'
	end;


(2) Using the Tag property. Set each label's Tag property (ie: 1 to 100)
and then
    you will be able to do something like:

	procedure TForm1.Button2Click(Sender: TObject);
	var
	   i: smallint;
	begin
	     for i := 0 to ComponentCount-1 do
	         if (Components[i] is TLabel) AND (Components[i].Tag = 3) then
	         begin
	            TLabel(Components[i]).Caption := 'This is the one!';
	            break;
	         end;
	end;

A:
How about iterating through all your components (Check your form's
Components property).
Use something like this:

var
  I: Integer;
begin
  for I := 0 to ComponentCount -1 do
     if Components[I] is TLabel then
       TLabel(Components[I]).Caption := 'This is the one!';
end;

A:
Put the labels into a TList. You can then use :

 TLabel(mylist.items[MyValue]).Caption := 'This is the one!';

A:
function LabelCaption(MyValue: Integer; Text: string) : Boolean;

function LabelCaption(MyValue: Integer; Text: string) : Boolean;
var
  LabelCount,
  Loop: Integer;
begin
  Result := False;
  LabelCount := 0;
  for Loop := 0 to ComponentCount - 1 do
  begin
    if Components[Loop] is TLabel then  { is it a label ? }
    begin
      INC(LabelCount);
      if LabelCount = MyValue then  { does it match my index value ? }
      begin
        Component[Loop].Caption := Text;
        Result := True;
      end;
    end;
  end;
end;

Call it as follows:-

   if LabelCaption(10, 'This is the one!') then
     WriteLn('Found it!');

The other approach which is more in line with what you want would be to
use a
StringList of some sort in which case you could do an IndexOf on the list
to find
your label. You could use the above Loop to add the label names to the
StringList.

This would allow you to do a lookup as follows:-

  MyLabels[MyLabels.IndexOf(Label10)].Caption := 'This is the one!';

OnkeyDown and Hot-key problem

Question

If got the following code, that works fine but...I get an annyoing beep.
I have the Form's KeyPreview set to True.

 If (Shift = [ssAlt]) then
    begin
      If Key = $4F then    {O}
        BitBtn1Click(BitBtn1);
      If Key = $55 then    {U}
        BitBtn4Click(BitBtn4);
      If Key = $58 then    {X}
        BitBtn2Click(BitBtn2);
      If Key = $48 then    {H}
        BitBtn3Click(BitBtn3);
    end;

Answer

I've found that there's a strange interaction between onKeyPress and
onKeyDown; setting key to 0 in onKeyDown doesn't necessarily kill it in
onKeyPress.  Apparently you're doing the above in onKeyDown; if you do it in
onKeyPress and set the key value to 0 the beeps should go away.
Alternatively, you can do something like this:

in onKeyDown:
   killKey := false;
   If key = $4f then begin
      killKey := true;
      bitBtn1Click (bitBtn1);
   end;

in onKeyPress:
   if killKey then key := #0;

Add an OnClick event to DBGrid

Question

How can I add an OnClick event to the Delphi 1.0 TDBGrid component?

Answer

TGroothuisGrid = class(TDBGrid) {!}
published
  property OnClick;
end;

That's all! The OnClick is already declared in TControl as a
protected property. All you have to do is publish it, register the
derived component (see ch. 8 op the Component Writer's Guide) and
use your component in stead of TDBGrid.

Events for components created at Run-Time

Question

How can i code an event for a component that has been
created at Run-Time? Is the windows message system the
only way?

Answer

You would manually create a method that has the same calling parameters as the
event you want to handle. Then you would manually set the OnXXX property to
point to the method you created.

i.e.

        TForm1 = class(TForm)
          procedure FormCreate(Sender: TObject);
        private
          FMyButton: TButton;
        protected
          procedure Button1Click(Sender: TObject); {Manually code this to
                                                    match}
                                                   {the TNotifyEvent
                                                    structure}
        end;

        procedure TForm1.FormCreate(Sender: TObject);
        begin
          FMyButton := TButton.Create;
          {Set the position, caption, etc here}
          FMyButton.OnClick := MyButtonClick;
        end;

        procedure TForm1.MyButtonClick(Sender: TObject);
        begin
          ShowMessage('Hey! You just clicked my button!');
        end;

Alignment in Listbox

Question

I'm developing a KWIC index and need to display the results in a listbox.
They have to line up around the central keyword, eg.
If I'm using a fixed font, this is easy, just blank pad on the left.  But if
I'm using a proportional font, it's much harder.  I could blank pad on the
left and used textWidth to check whether I'm near the middle, but because
character widths vary my keyword alignment is going to be off by a few pixels.

Answer

     You could take the TextWidth of the string before the keyword and
     adjust the position based on that.

     i.e.

     var
       J, TempInt, LongPrefixLen, CurrPrefixLen: Integer;

     begin
         {Calculate the TextWidth of the widest pre-keyword string}
         {Set CurrPrefixLen to the TextWidth of the pre-keyword of the
          Indexth string}
       LongPrefixLen := 0;
       for J := 0 to ListBox1.Items.Count-1 do
         with ListBox1.Canvas do
         begin
           TempInt:= TextWidth(Copy(Items[J],1,Pos(KeyString,Items[J]-1)));
           if LongPrefixLen < TempInt then
             LongPrefixLen:= TempInt;
           if J = Index then
             CurrPrefixLen:= TempInt;
         end;
           {PrevTextLeft - TextLeft = Where we want to write new item}
       TextOut(LongPrefixLen-CurrPrefixLen,Y,Items[I]);
     end;

Two columns in DBLookupComboBox

Question

I want to let shown 2 colums from one Datasource within a
TDBLookupComboBox in Delphi 2.0.

Answer

Try using anything like this:

DBLookupCombo1.LookupDisplay := 'Company;City;Country';

eg to specify more than one field to display, separate each field name with
a semicolon.

Change the color of a grid cell in a TDBGrid

Question

How do I change the color of a grid cell in a TDBGrid?

Answer

Enter the following code in the TDBGrid's OnDrawDataCell event:

Procedure TForm1.DBGrid1DrawDataCell(Sender: TObject; const Rect: TRect;
Field: TField; State: TGridDrawState);

begin

If gdFocused in State then
with (Sender as TDBGrid).Canvas do
begin

Brush.Color := clRed;
FillRect(Rect);
TextOut(Rect.Left, Rect.Top, Field.AsString);

end;

end;

Set the Default drawing to true.  With this, it only has to draw the
highlighted cell.  If you set DefaultDrawing to false, you must draw all the
cells yourself with the canvas
properties.

Cells' Position on DBGrid

Question

Is there any way to get the position of the selected cell (wich has the
focus) of a TDBGrid Component, in D1.  For example: left, top, rigth,
etc.

Answer

TCustomGrid defines the method CellRect, which is protected,
unfortunately. This means that it can only be called by TCustomGrid
itself or a descendant of TCustomGrid. There is way, though, to call
the method, though it's a bit tricky:

type
  TMyDBGrid = class(TDBGrid)
  public
    function CellRect(ACol, ARow: Longint): TRect;
  end;

function TMyDBGrid.CellRect(ACol, ARow: Longint): TRect;
begin
  Result := inherited CellRect(ACol, ARow);
end;

Then you can do the conversion by typecasting your DBGrid to
TMyDBGrid (This is possible, because the new CellRect is a static
method) and call CellRect:

Rectangle := TMyDBGrid(SomeDBGrid).CellRect(SomeColumn, SomeRow);

A:
procedure TfmLoadIn.DBGrid1DrawColumnCell(Sender: TObject;
  const Rect: TRect; DataCol: Integer; Column: TColumn;
  State: TGridDrawState);
const
  Disp = 2;	//Displacement to cause component to line up properly
begin
  inherited;
  if (gdFocused in State) then begin
    if (Column.FieldName = 'TYPEDescription') then begin
      dlTYPEDescription.Left := Rect.Left + DBGrid1.Left + Disp;
      dlTYPEDescription.Top := Rect.Top + DBGrid1.top + Disp;
      dlTYPEDescription.Width := Rect.Right - Rect.Left;
      dlTYPEDescription.Height := Rect.Bottom - Rect.Top;
      dlTYPEDescription.Visible := True;
    end;
  end;
end;

Listbox - OnChange

Question

I recently noticed the most annoying thing about TListBox -- no OnChange
event. I'v simulated the onchange event using the TTimer component, is
there an easier way?

Answer

I ran into that a few months back. I found one on the Supersite that
addressed this problem. There's not much to it.

UNIT Lbox;

INTERFACE

USES
  SysUtils, WinTypes, Messages, Classes, Controls, Graphics, Forms,
  Menus, StdCtrls;

Type
  TCngListBox = Class(TListBox)
    private
      FOnChange : TNotifyEvent;
      FLastSel : integer;
      procedure Click; override;
    protected
      procedure Change; Virtual;
    published
      property OnChange : TNotifyEvent read FOnChange write FOnChange;
    public
      constructor create(AOwner : TComponent); override;
  End;

Procedure Register;

IMPLEMENTATION

procedure TCngListBox.Change;
begin
  FLastSel := ItemIndex;
  if assigned(FOnChange) then FOnChange(self);
end;

procedure TCngListBox.Click;
begin
  inherited Click;
  if FLastSel <> ItemIndex then
     Change;
end;

constructor TCngListBox.Create;
begin
  Inherited Create(AOwner);
  FLastSel := -1;
end;

procedure Register;
begin
  RegisterComponents('FreeWare',[TCngListBox]);
end;

END.

TabbedNotbook enable / disable one page

Question

Can anybody tell me how I can disable/enable one tabbednote
among many tabbednotes from the same page?. The situation is
that the user logs in and depending their rights, i have to
disable/enable some of the tabbednotes.

Answer

  The simplest way to do this 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 (e.g. by removing
  all invalid characters, and prefixing with underscore if the
  first character is numeric).

  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 Tab}

  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; 

SETFOCUS in the StringGrid

Question

Does anyone know how to set the Focus to a specific Cell in the StringGrid,
and is there a way to know if the user Exit from a specific Cell, what
I mean here is there a similar event to ONExit to a Cell.

Answer

Grid.Row := YourRowNumber;
Grid.TopRow := YourFirstVisibleRow;
Grid.LeftCol := YourFirstVisibleColumn;

Different colors in DBGrid

Question

Can anyone tell me how to put different colors, in D1: DBGrid's row or cell,
when each time a customer buys a product a for example?

Answer

A:
Set defaultDrawing to false, then write your own onDrawDataCell to set the
color.  Something like this:

procedure Tform1.DBgrid1DrawDataCell
   (Sender: TObject; const Rect: TRect; Field: TField; State: TGridDrawState);
begin
   { decide color of text (font.color) and background (brush.color) }
   if (field = table1Status) then begin  { white on red }
      DBgrid1.canvas.font.color := clWhite;
      DBgrid1.canvas.brush.color := clRed;
      end
   else begin  { black on white }
      DBgrid1.canvas.brush.color := clWhite;
      DBgrid1.canvas.font.color := clBlack;
   end;

   { draw the cell }
   DBgrid1.canvas.textrect
      (rect,rect.left+2,rect.top+2,field.asString);
end;

A:
procedure TMainForm.CharGridDrawCell(Sender: TObject;
           Col, Row: Longint; Rect: TRect; State: TGridDrawState);
var
  TheText: string;
begin
  TheText := CharGrid.Cells[Col, Row];
  with CharGrid.Canvas do
  begin
  { Deal with background colour: }
    if gdFocused in State then
      Brush.Color := clYellow          {Focused background colour}
    else if gdSelected in State then
      Brush.Color := clOlive          {Selected background colour}
    else {cell is not focused or selected}
      if IntFromStr(TheText) <> 0 then
        Brush.Color := clNavy      {Highlighted background colour}
      else
        Brush.Color := clWhite;         {Normal background colour}
  { Deal with foreground colour: }
    if IntFromStr(TheText) <> 0 then
      Font.Color := clRed          {Highlighted foreground colour}
    else
      Font.Color := clNavy;             {Normal foreground colour}
    TextRect(Rect, Rect.Left + 2, Rect.Top + 2, TheText);
  end; {with CharGrid.Canvas}
end;

Cursor Pos in TRichEdit

Question

How can I determine the **current** Row and Col of a cursor in a TRichEdit?

Answer

Procedure TForm1.GetPosition(Sender: TRichEdit);
var
  iX,iY  : Integer;
  TheRichEdit : TRichEdit;
begin
  iX := 0; iY := 0;
  TheRichEdit := TRichEdit(Sender);
  iY := SendMessage(TheRichEdit.Handle, EM_LINEFROMCHAR, TheRichEdit.SelStart,
  0);
  iX := TheRichEdit.SelStart - SendMessage(TheRichEdit.Handle, EM_LINEINDEX,
  iY, 0);
  Panel1.Caption := IntToStr(iY + 1) + ':' + IntToStr(iX + 1) ;
end;

procedure TForm1.RichEditMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  GetPosition(RichEdit);
end;

procedure TForm1.RichEditKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  GetPosition(RichEdit);
end;

Trapping OnEnter in my component

Question

I'm trying to create a component. I wan't to be able to do some stuff
when the user enters (OnEnter) the component. How do I do that?

Answer

Components that can accept the input focus,  will have inherited methods
called DoEnter, and DoExit.

    Procedure DoExit; override;
    Procedure DOEnter; override;

procedure MyClassName.DoExit;
begin
  fIsFocused := FALSE;
  inherited DoExit;
end;

procedure MyClassName.DoEnter;
begin
  inherited DoEnter;
  fIsFocused := TRUE;
end;

Display popup menu

Question

How can I display popup menu?

Answer

A popup-menu pops on Screen coordinates.
The coordinates that you received from your event are probably
relative to the object that generated the message.
So you will have to use ClientToScreen to convert coordinates.

Here's an example of a popup-menu that launches when a user clicks on 
a node in a TTreeView. It's not exactly the same as you question,
but I don't feel like calculating with wParams right now. Sorry about
that, but I guess you can use the general idea.


procedure TfrmExplorer.TreeViewMouseDown(Sender: TObject; 
                                         Button: TMouseButton;
                                         Shift: TShiftState; 
                                         X, Y: Integer);
var P : TPoint;
begin
  if Button<>mbRight then exit;
  TreeMenu.AutoPopup := False;
  if TreeView.GetNodeAt(X,Y)<>NIL then
     begin
        TreeView.Selected := TreeView.GetNodeAt(X,Y);
        P.X := X; P.Y:=Y;
        P := TreeView.ClientToScreen(P);
        TreeMenu.Popup(P.X,P.Y);
     end;
end;

Canvas.TextWidth

Question

I'm trying to find the width of of text placed on a statusbar panel
and set the panel width to the size of the text ie.
         StatusBar1.Panels[1].Text := Edit1.Text;
         StatusBar1.Panels[1].Width :=
           StatusBar1.Canvas.TextWidth(StatusBar1.Panels[1].Text) + 10;
Unfortunately this does not work.  It works when the font is set to
size 8 but when I change the the font size to 12, it seems to calc the
width at the old font size. I'm using TT Arial as the font.
The Delphi help states the following about TextWidth:
"The TextWidth method returns the width in pixels of the string passed
in Text when rendered in the current font. You can use TextWidth to
determine whether a given string will fit in a particular space."

Answer

Try setting the panel Canvas' Font Size like this:
  With StatusBar1.Panels[1] do begin
    Text := Edit1.Text;
    Canvas.Font.Size := StatusBar1.Font.Size;
    Width := Canvas.TextWidth(Text) + 10;
  end;

TDBNavigator buttons

Question

Could someone please tell me how I can determine which button is pressed
on the TDBNavigator?

Answer

The OnClick event for the DBNavigator tells which button is pressed. You
can use it like this:
 
procedure TForm1.DBNavigator1Click(Sender: TObject; Button: 
TNavigateBtn);
var
  BtnName: string;
begin
  case Button of
    nbFirst  : BtnName := 'nbFirst';
    nbPrior  : BtnName := 'nbPrior';
    nbNext   : BtnName := 'nbNext';
    nbLast   : BtnName := 'nbLast';
    nbInsert : BtnName := 'nbInsert';
    nbDelete : BtnName := 'nbDelete';
    nbEdit   : BtnName := 'nbEdit';
    nbPost   : BtnName := 'nbPost';
    nbCancel : BtnName := 'nbCancel';
    nbRefresh: BtnName := 'nbRefresh';

  end;
  MessageDlg(BtnName + ' button clicked.', mtInformation, [mbOK], 0);
end;

Tabbed Notebook and visible components at several pages

Question

When I place components on a tab of a tabbed notebook, how
do I make some of those components visible to other tabs?

Answer

  Probably the best method is to place the shared components
  directly onto the Form, then move them over the TabbedNotebook
  so that they appear to be on all tabs.

  For any Tabs where the shared components should NOT appear, set
  the Visible property of the shared components appropriately in
  the TabbedNotebook's OnChange event handler.

How to empty a DBEdit

Question

I guess this is very simple, but I could not figure out how to make a DBEdit
control that's bound to a TDateField become empty, after I have put some date
in it.

Answer

Try myDbEdit.Text := '';
 or address the TField if you wish like this
TableNameMyField.Value := '';

A:
Table1.Edit;
Table1.FieldByName(DBEdit1.FieldName).Clear;

OnDraw Event for TStringGrid

Question

I want to be able to truncate strings in TStringGrid whenever it is longer
than the cell length. (for example:  'this string is way way too long for my
cell' -->  'this strin...'  However, I am not very experienced in drawing the
grid lines, etc...  Does someone have some code for drawing the grid lines?
I would then fill in the necessary code for drawing the text. Anyways, if no
code, I'd appreciate some help?

Answer

A:
This is code that I use to print a TMemoField into a TDBGrid. Override
the DrawCell method.

        Canvas.FillRect(ARect);
        R := ARect;
        WITH TMemoField(Field) DO
          DrawText(Canvas.Handle, PChar(Value), Length(Value), R,
            DT_WORDBREAK OR DT_NOPREFIX);


A:
  I think this code which I wrote with Delphi 1.0 may help you

procedure TForm1.StringGrid1DrawCell(Sender: TObject; Col, Row: Longint;
  Rect: TRect; State: TGridDrawState);
var  bufB : array[0..79] of Char;  algn : Word;
begin
  algn := 0;
  if  (Col = NumbColK) or (Col = PrceColK) or
      (Col = TtlColK)   then  algn := dt_Right;
  if  Row = 0  then  algn := dt_Center;
  if  algn = 0  then  Exit;
  StringGrid1.Canvas.FillRect(Rect);

  StrPCopy(bufB, StringGrid1.CellS[Col, Row]);
  Rect.Top := Rect.Top+2;  Rect.Right := Rect.Right-2;
  DrawText(StringGrid1.Canvas.Handle, bufB, -1, Rect, algn);
end;

   1) The first part is to set alignment if you want, 
and clear old text.
   2) The number -1 means bufB must be null terminate string
you can put any number such as 10 so it will limit your string to 10,
which is you want.

A:
Actually, depending on your application, you don't need to go that far.  If
you're filling the grid from another source, then just do this:

   grid.cells[col,row] :=
      trimWithDots (myString, form1.canvas, grid.widths[col]-2);

where trimWithDots is a function you write that trims myString based on the
width of the grid (in pixels) using the canvas's textWidth function,
something like this:

function trimWithDots (const myString: string; canvas: tCanvas; wid: integer):
    string;
begin
   result := myString;
   while canvas.textWidth (result) > wid do
      delete (result, length(result), 1);
end;

Of course, you can make it more sophisticated by trimming a bit more and
adding '...'.  For some funny reason, grid.canvas seems to give me improper
results, I've always used form1.canvas with good results.

If you aren't loading your grid from another source, you can do the same
thing but put it in your onDrawDataCell event.  This way the grid draws all
the lines for you, you only have to worry about drawing the contents.

Hiding TabbedNoteBook Pages

Question

Has anyone got any suggestions as to how to hide a tab in a
tabbed notebook at runtime without loosing the objects on the
page.

Answer

  The simplest way to do this is to remove the relevant
  page of the TabbedNotebook with something like:

      with TabbedNotebook do
         Pages.Delete(PageIndex);

  and retrieve the deleted page (when 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.

  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 Tab}

  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;  

Accessing memo field data

Question

My app has entry into a memo field (D1) and at times (like hitting arrow
keys, backspace, etc.) I need to go back and find the closest whitespace to
where the caret was.
I know that SelStart returns an integer of the position in the memo, but when
I try Text[SelStart] I get bogus answers if SelStart > 255.

Answer

Either use GetTextBuf method to retreive the entier buffer, or use the
follwing piece of code (modified to fit your needs ofcource).

procedure TForm1.SpeedButton1Click(Sender: TObject);

var
  LineNo : integer;
  ColNo  : integer;

begin
  LineNo:=SendMessage(Memo1.Handle,EM_LINEFROMCHAR,Memo1.SelStart,0);
  ColNo:=Memo1.SelStart;
  if LineNo>0 then begin
    While SendMessage(Memo1.Handle,EM_LINEFROMCHAR,ColNo,0)=LineNo do
      ColNo:=ColNo-1;
    ColNo:=Memo1.SelStart-ColNo-1;
  end else
    ColNo:=Memo1.SelStart;
  Panel1.Caption:='Row '+IntToStr(LineNo)+'  ;  Col '+IntToStr(ColNo);
  {Here you can get the text via Memo1.Lines[LineNo].Text[ColNo] ...}
end;

Warning! This code was developed under WinNT/D2 using a richedit
control. I have tested doing the same with a Memo Control in D1, but
that piece of code is at home. The code above is translated from memory
and not tested, but I think it should work. If you ever move to D2,
change the sendmessage calls to:
SendMessage(Memo1.Handle,EM_EXLINEFROMCHAR,0,ColNo)
                            ^^             ^^^^^^^
                            Added          Order Changed.

Word manipulation in TStringGrid

Question

How do you capitalize the first letter of every word in a field
as the user enters the characters?

Answer

A:
procedure TForm1.StringGrid1KeyPress(Sender: TObject; var Key: Char);
var s: String;
    c: Byte;
begin
  with StringGrid1 do
  s := Cells[Col, Row];
  if Length(s) = 0 then
   begin
     if Key in ['a'..'z'] then
      begin
        c := Ord(Key) - 32;
        Key := Chr(c);
      end;
     exit;
   end;
  if s[Length(s)] = ' ' then
    if Key in ['a'..'z'] then
     begin
       c := Ord(Key) - 32;
       Key := Chr(c);
     end;
end;

A:
In an onKeyPress event, do this:
    if length(field.text) = 0 then key := upCase (key);

Restrict the length of a TStringGrid field

Question

How do you restrict the length of a TStringGrid field?

Answer

A:
This is probably not very efficient, but should work: in your onKeyPress,
put the following:

   if key <> #8 then begin {allow backspace/Del}
      len := length(grid.cells[grid.col, grid.row]);
      if len >= your desired maximum length then begin
         messageBeep (0);
         key := #0;
      end;
   end;

A:
After getting the string s in the above code, do
 if Length(s) > maxlengthoffield then exit;

TextOut to a control's parent

Question

I have written and used a TEdit control that has its own caption. I did
this by creating a TLabel. The control works fine, but I would like to
eliminate the TLabel overhead and write the text directly to the TEdit's
parent (TForm, TScrollBox, TPanel, etc).  I have tried:
  With TCustomControl(parent).canvas.textout(x,y,'xxxxx');
but this gives the compile error that canvas is undeclared. In the
documentation, canvas is a property of TCustomControl. Typecasting to
TWinControl gives the same error.

Answer

The Canvas property exists in TCustomControl but is protected.
Since the canvas property encapsulates the windows HDC (Canvas.Handle) you
can  create a TCanvas object and assign the device context of the control
you want to draw on to the Handle property.  

For example:  

procedure AControl.DrawLabel(ACaption: TCaption);
var
              ACanvas: TCanvas;
              DC: HDC;
begin
              ACanvas:= TCanvas.Create;
              try         
                 WindowHandle:= parent.Handle;
                 DC := GetDeviceContext(WindowHandle);
                 ACanvas.Handle:= DC;
                 with ACanvas do
                       begin
                       end;
                 ACanvas.Handle:= 0;
                 ReleaseDC(WindowHandle, DC);
              finally
	     ACanvas.free;
              end;
end;

StatusBar - How to display Clock/Date/Keyboard Status

Question

I can't figure out how to display the system clock/date/keyboard stastus on
the Status Bar?

Answer

A:
Assuming you have you have a status bar with 4 panels defined, plus a timer
you can do this:

procedure TForm1.Timer1Timer(Sender: TObject);
begin
with StatusBar1 do
begin
 if GetKeyState(VK_CAPITAL) <> 0 then
  panels[0].text := ' CAP'
 else
  panels[0].text := '';
 if GetKeyState(VK_NUMLOCK) <> 0 then
  panels[1].text := ' NUM'
 else
  panels[1].text := '';
 if GetKeyState(VK_SCROLL) <> 0 then
  panels[2].text := ' SCRL'
 else
  panels[2].text := '';
 panels[3].text := ' ' +DateTimeToStr(now);
end;
end;
To format the date in different ways see the on line help under Date
routines. Note that the Text property is a string type thus you can't do
panels[0].text := DateTime(now); which is a double type.


A:
unit Status;

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, ExtCtrls, Menus, Gauges;

type
  TStatus = class(TCustomPanel)
  private
    FDate         : Boolean;
    FKeys         : Boolean;
    FTime         : Boolean;
    FResources    : Boolean;
    DateTimePanel : TPanel;
    ResPanel      : TPanel;
    ResGauge      : TGauge;
    CapPanel      : TPanel;

    NumPanel      : TPanel;
    InsPanel      : TPanel;
    HelpPanel     : TPanel;
    UpdateWidth   : Boolean;
    FTimer        : TTimer;
    procedure SetDate(A: Boolean);
    procedure SetKeys(A: Boolean);
    procedure SetTime(A: Boolean);
    procedure SetResources(A: Boolean);
    procedure SetCaption(A: String);
    Function  GetCaption: String;
    procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED;

  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure SetupPanelFields(ThePanel: TPanel);
    procedure SetupPanel(ThePanel: TPanel; WidthMask: String);
    procedure UpdateStatusBar(Sender: TObject);
  published
    property ShowDate: Boolean read FDate write SetDate default True;
    property ShowKeys: Boolean read FKeys write SetKeys default True;

    property ShowTime: Boolean read FTime write SetTime default True;
    property ShowResources: Boolean read FResources write SetResources
default True;
    property BevelInner;
    property BevelOuter;
    property BevelWidth;
    property BorderStyle;
    property BorderWidth;
    property Caption: string read GetCaption write SetCaption;

    property Color;
    property Ctl3D;
    property DragCursor;
    property DragMode;
    property Enabled;
    property Font;
    property ParentColor;
    property ParentCtl3d;
    property ParentFont;
    property ParentShowHint;
    property PopUpMenu;
    property ShowHint;
    property Visible;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Additional', [TStatus]);
end;

procedure TStatus.SetupPanelFields(ThePanel: TPanel);
begin
  with ThePanel do
    begin
      Alignment := taCenter;
      Caption := '';
      BevelInner := bvLowered;
      BevelOuter := bvNone;
      {Set all these true so they reflect the settings of the TStatus}
      ParentColor := True;
      ParentFont := True;

      ParentCtl3D := True;
    end;
end;

procedure TStatus.SetupPanel(ThePanel: TPanel; WidthMask: String);
begin
  SetupPanelFields(ThePanel);
  with ThePanel do
    begin
      Width := Canvas.TextWidth(WidthMask);
      Align := alRight;
    end;
end;

constructor TStatus.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Parent := TWinControl(AOwner);

  FTime := True;
  FDate := True;
  FKeys := True;
  FResources := True;
  {Force the status bar to be aligned bottom}
  Align := alBottom;
  Height := 19;
  BevelInner := bvNone;
  BevelOuter := bvRaised;
  {When UpdateWidth is set TRUE, status bar will recalculate panel widths once}
  UpdateWidth := True;
  Locked := True;
  TabOrder := 0;;
  TabStop := False;
  Font.Name := 'Arial';
  Font.Size := 8;
  {Create the panel that will hold the date & time}

  DateTimePanel := TPanel.Create(Self);
  DateTimePanel.Parent := Self;
  SetupPanel(DateTimePanel,'  00/00/00 00:00:00 am  ');
  {Create the panel that will hold the resources graph}
  ResPanel := TPanel.Create(Self);
  ResPanel.Parent := Self;
  SetupPanel(ResPanel,'                    ');
  {Create the 2 Gauges that will reside within the Resource Panel}
  ResGauge := TGauge.Create(Self);
  ResGauge.Parent := ResPanel;
  ResGauge.Align := alClient;

  ResGauge.ParentFont := True;
  ResGauge.BackColor := Color;
  ResGauge.ForeColor := clLime;
  ResGauge.BorderStyle := bsNone;
  {Create the panel that will hold the CapsLock state}
  CapPanel := TPanel.Create(Self);
  CapPanel.Parent := Self;
  SetupPanel(CapPanel,'  Cap  ');
  {Create the panel that will hold the NumLock state}
  NumPanel := TPanel.Create(Self);
  NumPanel.Parent := Self;
  SetupPanel(NumPanel,'  Num  ');

  {Create the panel that will hold the Insert/Overwrite state}
  InsPanel := TPanel.Create(Self);
  InsPanel.Parent := Self;
  SetupPanel(InsPanel,'  Ins  ');
  {Create the panel that will hold the status text}
  HelpPanel := TPanel.Create(Self);
  HelpPanel.Parent := Self;
  SetupPanelFields(HelpPanel);
  {Have the help panel consume all remaining space}
  HelpPanel.Align := alClient;
  HelpPanel.Alignment := taLeftJustify;

  {This is the timer that will update the status bar at regular intervals}
  FTimer := TTimer.Create(Self);
  If FTimer <> Nil then
    begin
      FTimer.OnTimer := UpdateStatusBar;
      {Updates will occur twice a second}
      FTimer.Interval := 500;
      FTimer.Enabled := True;
    end;
end;

destructor TStatus.Destroy;
begin
  FTimer.Free;
  HelpPanel.Free;

  InsPanel.Free;
  NumPanel.Free;
  CapPanel.Free;
  ResGauge.Free;
  ResPanel.Free;
  DateTimePanel.Free;
  inherited Destroy;
end;

procedure TStatus.SetDate(A: Boolean);
begin
  FDate := A;
  UpdateWidth := True;
end;

procedure TStatus.SetKeys(A: Boolean);
begin
  FKeys := A;
  UpdateWidth := True;
end;

procedure TStatus.SetTime(A: Boolean);
begin

  FTime := A;
  UpdateWidth := True;
end;

procedure TStatus.SetResources(A: Boolean);
begin
  FResources := A;
  UpdateWidth := True;
end;

{When we set or get the TStatus caption, it is affecting the HelpPanel
caption instead}
procedure TStatus.SetCaption(A: String);
begin
  HelpPanel.Caption := ' '+A;
end;

function TStatus.GetCaption: String;
begin

  GetCaption := HelpPanel.Caption;
end;

{This procedure sets the captions appropriately}
procedure TStatus.UpdateStatusBar(Sender: TObject);
begin
  if ShowDate and ShowTime then
    DateTimePanel.Caption := DateTimeToStr(Now)
  else
    if ShowDate and not ShowTime then
      DateTimePanel.Caption := DateToStr(Date)
    else
      if not ShowDate and ShowTime then

        DateTimePanel.Caption := TimeToStr(Time)
      else
        DateTimePanel.Caption := '';
  if UpdateWidth then with DateTimePanel do
    if ShowDate or ShowTime then
      Width := Canvas.TextWidth('  '+Caption+'  ')
    else
      Width := 0;
  if ShowResources then
    begin
      ResGauge.Progress := GetFreeSystemResources(GFSR_SYSTEMRESOURCES);

      if ResGauge.Progress < 20 then
        ResGauge.ForeColor := clRed
      else
        ResGauge.ForeColor := clLime;
    end;
  if UpdateWidth then
    if ShowResources then
      ResPanel.Width := Canvas.TextWidth('                    ')
    else
      ResPanel.Width := 0;
  if ShowKeys then
    begin
      if (GetKeyState(vk_NumLock) and $01) <> 0 then

        NumPanel.Caption := '  Num  ' else NumPanel.Caption := '';
      if (GetKeyState(vk_Capital) and $01) <> 0 then
        CapPanel.Caption := '  Cap  ' else CapPanel.Caption := '';
      if (GetKeyState(vk_Insert) and $01) <> 0 then
        InsPanel.Caption := '  Ins  ' else InsPanel.Caption := '';
    end;
  if UpdateWidth then
    if ShowKeys then

      begin
        NumPanel.Width := Canvas.TextWidth(' Num ');
        InsPanel.Width := Canvas.TextWidth(' Ins ');
        CapPanel.Width := Canvas.TextWidth(' Cap ');
      end
    else
      begin
        NumPanel.Width := 0;
        InsPanel.Width := 0;
        CapPanel.Width := 0;
      end;
  UpdateWidth := False;
end;

{This allows font changes to be detected so the panels will be adjusted}

procedure TStatus.CMFontChanged(var Message: TMessage);
begin
  inherited;
  UpdateWidth := True;
end;

end.

interface

implementation

end.

Icons in a Popup menus

Question

I would like to know how to implement icons on a popup menu in Delphi 2
similar to what windows 95 does from the start menu.  Any one know how
to do this?

Answer

A:
type
  TForm1 = class(TForm)
    MainMenu1: TMainMenu;
    File1: TMenuItem;    /**** Item in the Menu Bar ****/
    Open1: TMenuItem;     /**** Item int Menu File ****/
    procedure FormCreate(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
   {private declarations}
  public
   {public declarations}
   Icn, Txt, MnuItm: TBitmap;
  end;

procedure TForm2.FormCreate(Sender: TObject);
var R: TRect;
    HIcn: HIcon;
    Ic: TIcon;
    Index: Word;
    FileName: PChar;
begin
     /** Get the icon from some application **/
     Ic:=TIcon.Create;
     Ic.Handle:=ExtractAssociatedIcon(Hinstance, /* some path and filename
*/, Index);
     /** Create the bitmap for the text **/
     Txt:=TBitmap.Create;
     with Txt do
     begin
          Width:=Canvas.TextWidth(' Test');
          Height:=Canvas.TextHeight(' Tes');
          Canvas.TextOut(0,0,' Test');
     end;

     /** Copy the Icon to a bitmap to resize it. You can't resize a Icon **/
     Icn:=TBitmap.Create;
     with Icn do
     begin
          Width:=32;
          Height:=32;
          Brush.Color:=clBtnFace;
          Canvas.Draw(0,0,Ic);
     end;

     /** Create the final bitmap where you put the icon and the tex **/
     MnuItm:=TBitmap.Create;
     with MnuItm do
     begin
          Width:=Txt.Width+18;
          Height:=18;
          with Canvas do
          begin
               Brush.Color:=clBtnFace;
               Pen.Color:=clBtnFace;
               Brush.Style:=bsSolid;
               Rectangle(0,0,Width,Height);
               CopyMode:=cmSrcAnd;
               StretchDraw(Rect(0,0,16,16),Icn);
               CopyMode:=cmSrcAnd;
               Draw(16,8-(Txt.Height div 2),Txt);
          end;
     end;
end;

procedure TForm2.FormShow(Sender: TObject);
var
  ItemInfo: TMenuItemInfo;
  hBmp1   : THandle;
begin
     HBmp1:=MnuItm.Handle;
     with ItemInfo do
     begin
          cbSize        := SizeOf( ItemInfo );
          fMask         := MIIM_TYPE;
          fType         := MFT_BITMAP;
          dwTypeData    := PChar(MakeLong( hBmp1, 0 ));
     end;

     /** Replace the MenuItem Open1 with a final bitmap **/
     SetMenuItemInfo( GetSubMenu( MainMenu1.Handle, File1.MenuIndex ),
                   Open1.MenuIndex, true, ItemInfo );

end;

There is some problems on stretching the Icon and in the colors of the icon
in the Menu.
I'm also trying a better way but in this moment is this what I can get.


A:
The listing has been
modified to put the icons into the checked state of the menu (just like 95
does).  This also allows you to have a checked and unchecked state.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Menus,ShellAPI;

type
  TForm1 = class(TForm)
    MainMenu1: TMainMenu;
    File1: TMenuItem;
    Open1: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    Icn, MnuItm : TBitmap;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
var R: TRect;
    HIcn: HIcon;
    Ic: TIcon;
    Index: Word;
begin
     
{     /** Get the icon from some application **/}

     Index := 0;    { 11th icon in file }
     Ic:=TIcon.Create;
     Ic.Handle:=ExtractAssociatedIcon(Hinstance,
'c:\win95\system\shell32.dll', Index);
     
{     /** Copy the Icon to a bitmap to resize it. You can't resize a Icon
**/}
     Icn:=TBitmap.Create;

     with Icn do
     begin
          Width:=32;
          Height:=32;
          Canvas.Brush.Color := clbtnface;
          Canvas.Draw(0,0,Ic);
     end;

{     /** Create the final bitmap where you put the icon and the tex **/}
     MnuItm:=TBitmap.Create;
     with MnuItm do
     begin
          Width :=18;
          Height:=18;
          with Canvas do
          begin
               Brush.Color:=clbtnface;
               Pen.Color:=clbtnface;
               CopyMode:=cmSrcAnd;
               StretchDraw(Rect(0,0,16,16),Icn);
          end;
     end;
end;

procedure TForm1.FormShow(Sender: TObject);
var
  ItemInfo: TMenuItemInfo;
  hBmp1   : THandle;
begin
     HBmp1:=MnuItm.Handle;
     with ItemInfo do
     begin
          cbSize        := SizeOf( ItemInfo );
          fMask         := MIIM_CHECKMARKS;
          fType         := MFT_BITMAP;
          hBmpunChecked := HBmp1;     { Unchecked State }
          hBmpChecked   := HBmp1;     { Checked State }
     end;

{     /** Replace the MenuItem Open1 with a final bitmap **/}
     SetMenuItemInfo( GetSubMenu( MainMenu1.Handle, File1.MenuIndex ),
                   Open1.MenuIndex, true, ItemInfo );

end;

end.

Accessing Components in a TGroupBox

Question

I need to access component properties of components whose owner is a TGroupBox.
I know what the Groupbox name is, but will not know the names of the
Components it owns.  Is there any way to access the components font property
in the GroupBox and change it?

Answer

One of the properties of all controls is a pointer to the controls
contained within itself. This property is (tada) the Controls property
which is indexed as in an array. The count of the contained controls is
(tada) the ControlCount property. If you are going to access a property
or method that is not one of the TControl properties or methods, then
you will need to typecast the list item.

procedure DoSomethingWithAGroupBox;
var
  i : integer;
begin
  with AGroupBox do
    for i := 0 to ControlCount - 1 do
      if controls[i] is TEdit then
         TEdit(controls[i]).text := 'How about that!';
end;

The above example will work if the control is TEdit or a descendent of
TEdit such as TDBEdit or TMaskEdit. All objects can be typecast as one
of the objects that it is derived from or base class. You might be
tempted to say parent but parent is not a good term here as it means the
object which contains self.

Incrementing String Field

Question

I have a TStringfield that contains a letter that indicates a revision such
as 'A', 'B' ect. When adding a new revision I would like to automatically
increment this field to the next charcater. This seems like it should be easy
but since the field is of type 'string' and not 'char' I can't get it to work.
I tried the following:
  RevField.Text := chr(ord((RevTxt) + 1))
This fails because RevTxt is 'not of ordinal type' which I assume means it
must be type char.
I tried typecasting:
  RevField.Text := chr(ord(Char(RevTxt) + 1)))
and got an illegal typecast. Is their an easy way that I'm missing?

Answer

A:
The text property of controls is a string which is a special array of
char. You cannot typecast a char into a string. You can, however, access
each character in the string through its index.

Try this.

  var
    s : string;
  begin
    s := RevField.text;
    s[1] := chr(ord(s[1]) + 1);
    RevField.text := s;
  end;


A:
  The 2 problems are:

1.  You need to extract the char from the string to be able to
    increment it.

2.  Whilst you can access the individual characters of a string
    by subscripting, this is not allowed with properties such as
    TStringField's Text property.

    It would probably be best to write a small function.  For
    example, if the revision letter is always the final character
    in the string you could write:

      function IncrementTrailingVersionLetter(Str: string): string;
      begin
        Str[Length(Str)] := Char(Ord(Str[Length(Str)]) + 1);
        IncrementTrailingVersionLetter := Str;
      end;

    and then use it as follows:

      with RevField do
        Text := IncrementTrailingVersionLetter(Text);

Using OnHint events among multiple forms

Question

How can I use OnHint events among multiple forms?

Answer

Delphi's Online Help and Visual Component Library Reference
describe an example for processing TApplication's OnHint event.
The example shows how a panel can be used to display hints
associated with other components. As the example sets the
Application's OnHint method in the Form's OnCreate event, a
program involving more than one form will have difficulty using
this technique.

Moving the assignment of OnHint from the Form's OnCreate event
to its OnActivate method will allow different forms involved in
the application to treat Hints in their own way.

Here is an altered form of the source code presented in the
Online Help and the VCL Reference.

type
  TForm1 = class(TForm)
    Button1: TButton;
    Panel1: TPanel;
    Edit1: TEdit;
    procedure FormActivate(Sender: TObject);
  private
    { Private declarations }
  public
    procedure DisplayHint(Sender: TObjject);
  end;

implementation

{$R *.DFM}

procedure TForm1.DisplayHint(Sender: TObject);
begin
  Panel1.Caption := Application.Hint;
end;

procedure TForm1.FormActivate(Sender: TObject);
begin
  Application.OnHint := DisplayHint;
end;


Moving to a tab by name on a tabset

Question

How can I move to a tab by name on a tabset?

Answer

Place a Tabset(TabSet1) and an Edit (Edit1) on
your form. Change the Tabset's Tabs Property in
the String List Editor to include 4 Tabs:
             Hello, 
             World, 
             Of,
             Delphi,
   
Change Edit1's onChange event to:

procedure TForm1.Edit1Change(Sender: TObject);
var
  I : Integer;
begin
  for  I:= 0 to tabset1.tabs.count-1 do
   if  edit1.text = tabset1.tabs[I] then
     tabset1.tabindex:=I;
end;

If You type any of the Tabs names in edit1 it 
will focus on the appropriate tab.


Control font styles

Question

How can i control font styles of selected Edits?

Answer

{
  This code will change the font style of a Edit
  when selected. This code could be implemented to
  control font style on other objects.

  With a Edit(Edit1) and a ListBox(ListBox1) on a form
  Add the following Items to the ListBox:
    fsBold
    fsItalic
    fsUnderLine
    fsStrikeOut
}
procedure TForm1.ListBox1Click(Sender: TObject);
var
  X : Integer;
type
  TLookUpRec = record
    Name: String;
    Data: TFontStyle;
  end;
const
  LookUpTable: array[1..4] of TLookUpRec =
  ((Name: 'fsBold'; Data: fsBold),
   (Name: 'fsItalic'; Data: fsItalic),
   (Name: 'fsUnderline'; Data: fsUnderline),
   (Name: 'fsStrikeOut'; Data: fsStrikeOut));
begin
  X := ListBox1.ItemIndex;
  Edit1.Text := ListBox1.Items[X];
  Edit1.Font.Style := [LookUpTable[ListBox1.ItemIndex+1].Data];
end;


Removing the vertical scrollbar from a TDBGrid

Question

How to remove the vertical scrollbar from a TDBGrid?

Answer

{
 In order to remove the vertical scrollbar from a TDBGrid component, 
 you must override its Paint method.  Inside the Paint method you 
 must call the SetScrollRange API procedure to set the min and max 
 scroll values to zero (this disables the scrollbar), and then call
 the inherited Paint.  The code below is a unit containing a new 
 component called TNoVertScrollDBGrid that does this.  You can copy
 the code into a file called NEWGRID.PAS, and add it to the component
 library as a custom component.
}

unit Newgrid;

interface

uses
  WinTypes, WinProcs, Classes, DBGrids;

type
  TNoVertScrollDBGrid = class(TDBGrid)
  protected
    procedure Paint; override;
  end;

procedure Register;

implementation

procedure TNoVertScrollDBGrid.Paint;
begin
  SetScrollRange(Self.Handle, SB_VERT, 0, 0, False);
  inherited Paint;
end;

procedure Register;
begin
  RegisterComponents('Data Controls', [TNoVertScrollDBGrid]);
end;

end.


Getting a device context for a control

Question

Does anyone know how to get a DC for a delphi VCL.  I am trying to use
the API function StretchBlt which requires a destDC and a sourceDC. 
How do I tell windows to use my TImage or TShape for this purpose?

Answer

{Bitmap in a TImage}
HDC := TImage.Picture.bitmap.canvas.handle;

A DC is anything with a Canvas.handle.


Disabling DBNavigator buttons

Question

How to disable DBNavigator buttons?

Answer

{ DBNavigator enhancement: allows developer to enable and disable
  individual buttons via EnableButton and DisableButton methods }

unit GNav;

interface

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

type
  TMyNavigator = class(TDBNavigator)
  public
     procedure EnableButton(Btn : TNavigateBtn) ;
     procedure DisableButton(Btn : TNavigateBtn) ;
  end;

procedure Register;

implementation

procedure TMyNavigator.EnableButton(Btn : TNavigateBtn) ;
begin
     Buttons[Btn].Enabled := True ;
end ;

procedure TMyNavigator.DisableButton(Btn : TNavigateBtn) ;
begin
     Buttons[Btn].Enabled := False ;
end ;

procedure Register;
begin
  RegisterComponents('Samples', [TMyNavigator]);
end;

end.


DBGrid that shows images

Question


Answer

{
// DBPICGRD.PAS (C) 1995 W. Raike
//              ALL RIGHTS RESERVED.
//
//    DESCRIPTION:
//      Data-aware grid that can display graphic fields.
//    REVISION HISTORY:
//      15/04/95  Created.    W. Raike
}

unit DBPicGrd;

interface

uses
  DBGrids, DB, DBTables, Grids, WinTypes, Classes, Graphics;

type
  TDBPicGrid = class(TDBGrid)
  protected
    procedure DrawDataCell(const Rect: TRect;
      Field: TField; State: TGridDrawState); override;
  public
    constructor Create(AOwner : TComponent); override;
  published
    property DefaultDrawing default False;
  end;

procedure Register;

implementation

constructor TDBPicGrid.Create(AOwner : TComponent);
begin
  inherited Create(AOwner);
  DefaultDrawing := False;
end;

procedure TDBPicGrid.DrawDataCell(const Rect: TRect; Field: TField;
State: TGridDrawState);
var
  bmp : TBitmap;
begin
  with Canvas do
  begin
    FillRect(Rect);
    if Field is TGraphicField then
        try
          bmp := TBitmap.Create;
          bmp.Assign(Field);
          Draw(Rect.Left, Rect.Top, bmp);
        finally
          bmp.Free;
        end
    else
      TextOut(Rect.Left, Rect.Top, Field.Text);
  end;
end;

procedure Register;
begin
  RegisterComponents('Custom', [TDBPicGrid]);
end;

end.


Using canvas in user-defined components

Question

How to use canvas in user defined components?

Answer

Suggestion for how to use Canvas in a homegrown component:

TScrollingPaintBox = class(TScrollingWinControl)
  private
    FCanvas: TCanvas;
  public
    constructor Create(aOwner:TComponent); override;
    destructor Destroy; override;
    property Canvas: TCanvas read FCanvas;
end;

constructor TScrollingPaintBox.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FCanvas := TControlCanvas.Create;
  TControlCanvas(FCanvas).Control := Self;
end;

destructor TScrollingPaintBox.Destroy;
begin
  FCanvas.Free;
  inherited Destroy;
end;

A TControlCanvas is important because it creates a DC that belongs to the 
HWND of the control.  Also, override is important on your constructor 
and destructor to insure that they are actually called. 


Accessing other components from a base component

Question

How do I access other components in my application from within [my Base] component?

Answer

The Components[] list property exists in all descendants of TComponent
and is used to store references to any owned components.  When one
calls "mycomponent := TSomeComponent.Create(aComponent)", mycomponent
is placed in aComponent's Components[] list.  In most cases, the form
is specified as a component's owner in the Create method, in which case
the component is placed in the form's Components[] list.

The FindComponent() method (mentioned elsewhere) only searches the current
component's Components[] list.  If the object you want to find is owned
by a different component, you will have to scan the other component's
list.

Depending on how you create your Base and other components, you may have
to implement a recursive search algorithm that starts at the top of the
ownership tree (probably with the form), and then descends into the tree,
searching the Components[] list of each component it finds until the desired
object is reached.

A nice alternative is just to always specify your Base component as the
owner of all your other sub-components when you create them.  Then your
search of the Base component's Components[] list should work.


Extending DBGrid

Question

How to extend DBGrid?

Answer

{
Here's a TDBGrid derivative that exposes the Col, Row and Canvas properties
as well as the CellRect method.  This is extremely useful if you would like
to, for example, pop a dropdown list over a cell when the user enters.
}

unit VUBComps;

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, Grids, DBGrids, DB, Menus;

type
  TDBGridVUB = class(TDBGrid)
  private
    { Private declarations }
  protected
    { Protected declarations }
  public
    property Canvas;
    function CellRect(ACol, ARow: Longint): TRect;
    property Col;
    property Row;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('VUBudget', [TDBGridVUB]);
end;

function TDBGridVUB.CellRect(ACol, ARow: Longint): TRect;
begin
  Result := inherited CellRect(ACol, ARow);
end;

end.


Creating a resizeable (elastic) panel

Question

How to realize a resizeable (elastic) panel?

Answer

{
  Here's the source code for a resizable panel.  Give the panel an align 
  property of alClient, throw some controls on it, and watch them resize 
  at run time when you resize the form.  There is some code that prohibits 
  resizing during design time, but this can be taken out.  This may not be 
  perfect, because I threw it together in a few minutes, but it's worked 
  for me so far.
}

unit Elastic;

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, ExtCtrls;

type
  TElasticPanel = class( TPanel )
  private
     FHorz, FVert: boolean;
     nOldWidth, nOldHeight: integer;
     bResized: boolean;
  protected
     procedure WMSize( var message: TWMSize ); message WM_SIZE;
  public
     nCount: integer;
     constructor Create( AOwner: TComponent ); override;
  published
     property ElasticHorizontal: boolean read FHorz write FHorz default TRUE;
     property ElasticVertical: boolean read FVert write FVert default TRUE;
  end;

procedure Register;

implementation

constructor TElasticPanel.Create( AOwner: TComponent );
begin
  inherited Create( AOwner );
  FHorz := TRUE;
  FVert := TRUE;
  nOldWidth := Width;
  nOldHeight := Height;
  bResized := FALSE;
end;

procedure TElasticPanel.WMSize( var message: TWMSize );
var
  bResize: boolean;
  xRatio: real;
  i: integer;
  ctl: TWinControl;
begin
  Inc( nCount );
  if Align = alNone then
     bResize := TRUE
  else
     bResize := bResized;
  if not ( csDesigning in ComponentState ) and bResize then
     begin
        if FHorz then
           begin
              xRatio := Width / nOldWidth;
              for i := 0 to ControlCount - 1 do
                 begin
                    ctl := TWinControl( Controls[i] );
                    ctl.Left := Round( ctl.Left * xRatio );
                    ctl.Width := Round( ctl.Width * xRatio );
                 end;
           end;
        if FVert then
           begin
              xRatio := Height / nOldHeight;
              for i := 0 to ControlCount - 1 do
                 begin
                    ctl := TWinControl( Controls[i] );
                    ctl.Top := Round( ctl.Top * xRatio );
                    ctl.Height := Round( ctl.Height * xRatio );
                 end;
           end;
     end
  else
     begin
        nOldWidth := Width;
        nOldHeight := Height;
     end;
  bResized := TRUE;
  nOldWidth := Width;
  nOldHeight := Height;
end;

procedure Register;
begin
  RegisterComponents('Additional', [TElasticPanel]);
end;

end.


Assigning OnClick events for menu items created at run-time

Question

How to assign OnClick events for menu items created at runtime?

Answer

Since the OnClick method is a property, when you dynamically create the
menu item, also assign the name of on OnClick handler:

	theMenuitem.OnClick := TheOnClickHandler;

Then, in the OnClick handler, you cast the sender into a TMenuItem and
read the name:

procedure theform.TheOnClickHandler(Sender: TObject);
var
	fName: String;
begin
	fName := TMenuItem(Sender).name;
	...
end;


Setting boundaries for newly created controls

Question

How to set boundaries for newly created controls?

Answer

If you are designing a new Delphi control and you need to catch
or limit changes to Left, Top, Width, or Height, there is one
simple way to do it.  However, I have yet to see the slightest
hint of how to do it in the Delphi documentation (including
the CWG).

The key to catching changes to Left, Top, Width, Height, and
even BoundsRect in Delphi is the SetBounds() method (found
in TControl and all descendants).  SetBounds() is a virtual
function used to set the position and size of a control
all in one easy step.  However, what they do not tell you
in the documentation is that TControl.SetBounds() is called
by TControl's SetLeft(), SetTop(), SetWidth(), and SetHeight()
property access methods every time a value is assigned to
the Left, Top, Width, Height, and BoundsRect properties.

Thus, to catch changes to these properties, simply override
the SetBounds() method, do whatever you need with the new
values, and then pass them on to the inherited SetBounds()
method.

In the following example, I have a control that automatically
updates its X & Y custom properties to refer to the center
of the control whenever Left, Top, Width, Height, or BoundsRect
are changed.  Conversely, Left and Top will be updated whenever
X & Y are changed:

type
  TMyControl = class(TControl)
  private
    FX, FY : integer;
    {property access methods}
    procedure SetX(value : integer);
    procedure SetY(value : integer);
    ...
  public
    procedure SetBounds(aLeft, aTop, aWidth, aHeight : integer); override;
    ...
    property X : integer read FX write SetX;
    property Y : integer read FY write SetY;
  end;
...

procedure TMyControl.SetX(value : integer);
begin
  if FX <> value then 
    SetBounds(value - Width div 2, Top, Width, Height);
end;

procedure TMyControl.SetY(value : integer);
begin
  if FY <> value then
    SetBounds(Left, value - Height div 2, Width, Height);
end;

procedure TMyControl.SetBounds(aLeft, aTop, aWidth, aHeight : integer);
begin
  {Go ahead and let SetBounds() do its thing...}
  inherited SetBounds(aLeft, aTop, aWidth, aHeight);
  {Now adjust FX and FY according to our new bounds.}
  FX := Width div 2;
  FY := Height div 2;
end;

Also not mentioned in the documentation is the fact that the
FLeft, FTop, FWidth, and FHeight private fields that TControl
uses to keep track of its bounding rectangle are not updated
in the corresponding SetLeft(), SetTop(), etc. property access
methods.  These variables, in fact, do not get updated anywhere
except in TControl's SetBounds() method (as with FX and FY in
the above example).

So, to limit changes to the bounds of your control, you can
override SetBounds() to check and modify any of the properties
before passing the values on to the inherited SetBounds()
method.

In the following example, I have a control that limits its
width and height to no more than 100 pixels:

type
  TMyControl = class(TControl)
    ...
  public
    procedure SetBounds(aLeft, aTop, aWidth, aHeight : integer); override;
    ...
  end;

...
procedure TMyControl.SetBounds(aLeft, aTop, aWidth, aHeight : integer);
begin
  if aWidth > 100 then
    aWidth  := 100;
  if aHeight > 100 then
    aHeight := 100;
  inherited SetBounds(aLeft, aTop, aWidth, aHeight);
end;



Expanding a path to a TUutlineNode referenced by index

Question

How to expand a path to a TOutlineNode referenced by index?

Answer

The purpose I wrote this routine was, that I had an index from a TOutlineNode
(which was the result of search) and wanted to expand a path to the node
without expanding unnecessary trees.

The following routine accepts an index as a parameter and expands the path
to the TOutlineNode with this index.

The routine assumes a TOutline object named Outline.

var
  Outline: TOutline;

procedure TSearchDlg.ExpandPathToFoundItem(const FoundItemIndex: Longint);
{------------------------------------------------------------------------------
 Expands a path to a given item (item is specified by the index number). Only
 the parents needed to get to the specified item will be expanded.
 -----------------------------------------------------------------------------}
var
  ItemIndex:   Longint;
  Found:       Boolean;
  LastCh:      Longint;
  Path:        String;
  ItemText:    String;
  SepPos:      Integer;
  OldSep:      String;
begin
  {Save the old ItemSpearator}
  OldSep:=Outline.ItemSeparator;
  {Set the new ItemSeparator}
  Outline.ItemSeparator:='\';
  {Get the full path to the TOutlineNode and add a '\'. This is done, because it
   simplifies the whole algorithm}
  Path:=Outline.Items[FoundItemIndex].FullPath+'\';
  {As long as the end of the path has not been reached}
  while Length(Path) > 0 do begin
    {Determine the position of the first '\' in the path}
    SepPos:=Pos('\',Path);
    {Isolate the TOutlineNode item}
    ItemText:=Copy(Path,1,SepPos-1);
    {Determine the index of the TOutlineNode}
    ItemIndex:=Outline.GetTextItem(ItemText);
    {Expand it}
    Outline.Items[ItemIndex].Expand;
    {Cut the expanded TOutlineNode from the string}
    Path:=Copy(Path,SepPos+1,Length(Path)-SepPos+1);
  end;
  {Restore original ItemSeparator}
  Outline.ItemSeparator:=OldSep;
end;


DETAILS

Let's assume the full path to the desired item is:

        "My Computer\Hardware\SoundCard\Base Adress"

The first step returns the above path. Then the substring "My Computer" is
isolated. Then the index of the TOutlineNode "My Computer" is determined by
using the "GetTextItem" method. The "Expand" method expands this tree.
Afterwards "My Computer" is cut from the path resulting in the new path
"Hardware\SoundCard\Base Adress".

Then the index of "Hardware" is determined, expanded and again, cut away.
This procedure repeats until there is no path left to expand. Then the path
to the given TOutlineNode will be expanded.


Associating a string with each component

Question

Is there a way to associate a string with each component?

Answer

Since the Tag property is a longint, you can type cast it 
as a Pointer or PChar.  So, you can basically store a pointer 
to a record by using the Tag property.

Note:  You're not going to be able to store the string, or 
pointer rather, at design time. This is something you'll have 
to do at run time. Take a look at this example:

 var
  i: integer;
 begin
   for i := 0 to ComponentCount - 1 do

     if Components[i] is TEdit then
       Components[i].Tag := LongInt(NewStr('Hello '+IntToStr(i)));
 end;

Here, I loop through the components on the form.  If the 
component is a TEdit, I assign a pointer to a string to its Tag 
property.  The NewStr function returns a PString (pointer to a 
string).  A pointer is basically the same as a longint or 
better, occupies the same number of bytes in memory. Therefore, 
you can type cast the return value of NewStr as a LongInt and 
store it in the Tag property of the TEdit component.  Keep in 
mind that this could have been a pointer to an entire record.  
Now I'll use that value:

 var
  i: integer;
 begin
   for i := 0 to ComponentCount - 1 do
     if Components[i] is TEdit then begin
       TEdit(Components[i]).Text := PString(Components[i].Tag)^;
       DisposeStr(PString(Components[i].Tag));
     end;
 end;

Here, again I loop through the components and work on only the 
TEdits.  This time, I extract the value of the component's Tag 
property by typecasting it as a PString (Pointer to a string) 
and assigning that value to the TEdit's Text property. Of 
course, I must dereference it with the caret (^) symbol.  Once 
I do that, I dispose of the string stored in the edit 
component.  Important note: if you store anything in the 
TEdit's Tag property as a pointer, you are responsible for 
disposing of it also.

FYI, Since Delphi objects are really pointers to class 
instances, you can also store objects in the Tag property. As 
long as you remember to Free them.

Three methods spring to mind to use Tags to access strings that 
persist from app to app.

1.  If your strings stay the same forever, create a string 
resource in Resource Workshop (or equiv) and use the Tags as 
indexes into your string resource.

2.  Use TIniFile and create a section for your strings, and 
give each string a name with number so that your ini file has a 
section like this:

[strings]
string1=Aristotle
string2=Plato
string3=Well this is Delphi, after all

Then you can fetch them back out this way:

  var s1: string;
  ...
  s1 := IniFile1.ReadString('strings', 'string'+IntToStr(Tag), '');

3.  Put your strings into a file, with each followed by a 
carriage return.  Read them into a TStringList.  Then your Tags 
become an index into this stringlist:

  StringList1.LoadFromFile('slist.txt');
  ...
  s1 := StringList1[Tag];

Given the way Delphi is set up, I think the inifile method is easiest.



DISCLAIMER: You have the right to use this technical information
subject to the terms of the No-Nonsense License Statement that
you received with the Borland product to which this information
pertains.


Populating TDBComboBoxes and TDBListBoxes

Question


Answer

Most of Delphi's data aware components will populate 
themselves after they are wired up to a open dataset.  However 
DbListboxes and DbComboboxes do not display this characteristic.  
These two components are not for displaying your datasets, but 
filling them.  Use of these components is straight forward.  
When you update your table, the value of the DbListbox or 
DbCombobox will be posted in the appropriate field.

Filling the DbCombobox or DbListbox the same as filling normal 
comboboxs or listboxes.  The lines of text in a listbox or 
combobox are really a tstring list.  The "Items" property of 
the given component holds this list.  Use the "Add" method for 
adding items to a tstring. If you want to use data  types other 
than strings they must be converted at run time. If your list 
has a blank line at the end, consider setting the 
"IntegralHeight" property to True.


Filling a DbListbox with 4 lines programmatically might look 
similar to this:

     DbListbox1.items.add('line one');
     DbListbox1.items.add('line two');
     DbListbox1.items.add('line three');
     DbListbox1.items.add('line four');

Filling a DbListbox at design time requires using the object 
inspector.  By double clicking on the components "Items" 
property, you can bring up the "String List Editor" and input 
the desired rows.

Unfortunately, if a combobox is filled this way, there is not 
default value.  Setting a DbComboboxs "text" property will 
achieve this result.  (the "text" property is not available in 
the object inspector, so it must be set programmatically).  
Setting the default value to the first value in the 
DbCombobox's list looks like this:        

DbCombobox1.text := DbCombobox1.items[0];

Often it is useful to fill a DBListBox from a dataset.  This 
can be done using loop:

procedure TForm1.FormCreate(Sender: TObject);
begin
  with table2 do begin
    open;
    while not EOF do
    begin
      DBlistbox1.items.add(FieldByName('name').AsString); 
      next;
    end;
  end;
end;



DISCLAIMER: You have the right to use this technical information
subject to the terms of the No-Nonsense License Statement that
you received with the Borland product to which this information
pertains.


Activating horizontal scrollbar for listboxes

Question

Can someone describe how to activate the horizontal scrollbar in a 
listbox. I need to do this programatically.

Answer

try this:

  SendMessage(ListBox.Handle, LB_SetHorizontalExtent, , 0);


Click and move components at run-time

Question

How can I program a component, such as a TPanel, so that I 
can move it around with a click and drag of the mouse?

Answer

This code goes on the OnMouseDown event of the component in 
question (a TPanel in this case):

procedure TForm1.Panel1MouseDown(Sender: TObject; Button: TMouseButton;
    Shift: TShiftState; X, Y: Integer);
const
  SC_DragMove = $F012;  { a magic number }
begin
  ReleaseCapture;
  panel1.perform(WM_SysCommand, SC_DragMove, 0);
end;



DISCLAIMER: You have the right to use this technical information
subject to the terms of the No-Nonsense License Statement that
you received with the Borland product to which this information
pertains.


Validating input in TEdit components

Question

How can I validate input in my TEdit components?

Answer

   Assuming you're using regular TEdit components (as opposed to TDBEdit
   components), the best place to validate input is the OnExit event of
   the TEdit.  This event fires whenever focus leaves the component.
   
   Typically, when the user enters some invalid text into the control,
   you want to issue a warning to the user, and set the focus back
   to the control.  A difficulty arises, however, when you attempt
   to set focus to a particular control from an OnExit event handler.
   Since Windows is "in the middle of" setting focus from one control
   to another during OnExit, you will see irregular behavior from
   controls if you attempt to change focus at that time.

   The solution is to post a message to your form in the TEdit's OnExit
   event handler.  This user-defined posted message will indicate that
   the coast is clear to begin validating input.  Since posted messages
   are placed at the end of the message queue, this gives Windows the
   opportunity to complete the focus change before you attempt to
   change the focus back to another control.

   Attatch is a unit and text representation of a DFM (form) file
   which demonstrates this technique.

{ *** BEGIN CODE FOR UNIT1.PAS *** }
unit Unit1;

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, Mask;

const
  { User-defined message }
  um_ValidateInput = wm_User + 100;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    Edit4: TEdit;
    Button1: TButton;
    MaskEdit1: TMaskEdit;
    procedure Edit1Exit(Sender: TObject);
  private
    { User-defined message handler }
    procedure ValidateInput(var M: TMessage); message um_ValidateInput;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.ValidateInput(var M: TMessage);
begin
  { The following line is my validation.  I want to make sure the }
  { first character is a lower case alpha character.  Note the    }
  { typecast of lParam to a TEdit. }
  if not (TEdit(M.lParam).Text[1] in ['a'..'z']) then begin
    ShowMessage('Bad input');              { Yell at the user }
    TEdit(M.lParam).SetFocus;              { Set focus back   }
  end;
end;

procedure TForm1.Edit1Exit(Sender: TObject);
begin
  { Post a message to myself which indicates it's time to  }
  { validate the input.  Pass the TEdit instance (Self) as }
  { the message lParam. }
  PostMessage(Handle, um_ValidateInput, 0, longint(Sender));
end;

end.
{ *** END CODE FOR UNIT1.PAS *** }

{ *** BEGIN CODE FOR UNIT1.DFM *** }
object Form1: TForm1
  Left = 200
  Top = 99
  Width = 318
  Height = 205
  Caption = 'Form1'
  Font.Color = clWindowText
  Font.Height = -13
  Font.Name = 'System'
  Font.Style = []
  PixelsPerInch = 96
  TextHeight = 16
  object Edit1: TEdit
    Left = 32
    Top = 32
    Width = 121
    Height = 24
    TabOrder = 0
    Text = 'Edit1'
    OnExit = Edit1Exit
  end
  object Edit2: TEdit
    Left = 160
    Top = 32
    Width = 121
    Height = 24
    TabOrder = 1
    Text = 'Edit2'
    OnExit = Edit1Exit
  end
  object Edit3: TEdit
    Left = 32
    Top = 64
    Width = 121
    Height = 24
    TabOrder = 2
    Text = 'Edit3'
    OnExit = Edit1Exit
  end
  object Edit4: TEdit
    Left = 160
    Top = 64
    Width = 121
    Height = 24
    TabOrder = 3
    Text = 'Edit4'
    OnExit = Edit1Exit
  end
  object Button1: TButton
    Left = 112
    Top = 136
    Width = 89
    Height = 33
    Caption = 'Button1'
    TabOrder = 4
  end
end
{ *** END CODE FOR UNIT1.DFM *** }


DISCLAIMER: You have the right to use this technical information
subject to the terms of the No-Nonsense License Statement that
you received with the Borland product to which this information
pertains.


Different colored characters in a string grid

Question


Answer

This unit will show how to have text in a string grid where the 
characters are different colors.

unit Strgr;

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, Grids, StdCtrls, DB;

type
  TForm1 = class(TForm)
    StringGrid1: TStringGrid;
    procedure StringGrid1DrawCell(Sender: TObject; Col, Row: Longint;
      Rect: TRect; State: TGridDrawState);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.StringGrid1DrawCell(Sender: TObject; Col, Row: Longint;
  Rect: TRect; State: TGridDrawState);
const
  CharOffset = 3;
begin
  with StringGrid1.canvas do
  begin
    font.color := clMaroon;
    textout(rect.left + CharOffset, rect.top + CharOffset, 'L');
      font.color := clNavy;
    textout(rect.left + CharOffset + TextWidth('L'),
      rect.top + CharOffset, 'loyd');
  end;
end;

end.


ISBN validation

Question


Answer

ISBNs (or International Standard Book Numbers) are mystical code numbers
that uniquely identify books. The purpose of this article is to remove the
mystery surrounding the structure of the ISBN, allowing applications to
perform data validation on entered candidate ISBNs.

ISBNs are composed of thirteen characters, limited to the number
characters "0" through "9", the hyphen, and the letter "X". This thirteen-
character code is divided into four parts, each separated by hyphens:
group identifier, publisher identifier, book identification for the
publisher, and the check digit. The first part (group identifier) is used
to identify countries, geographical regions, languages, etc. The second
part (publisher identifier) uniquely identifies the publisher. The third
part (book identifier) uniquely identifies a given book within a
publisher's collection. The fourth and final part (check digit) is used
with the other digits in the code in an algorithm to derive a verifiable
ISBN. The number of digits in the first three parts of an ISBN may contain
a variable number of digits, but the check digit will always consist of a
single character (between "0" and "9", or "X" for a value of 10) and the
ISBN as a whole will always consists of thirteen characters (ten numbers
plus the three hyphens dividing the four parts of the ISBN).

The ISBN 3-88053-002-5 breaks down into the parts:

  Group:       3
  Publisher:   88053
  Book:        002
  Check Digit: 5

An ISBN can be verified to be a valid code using a simple mathematical
algorithm. This algorithm takes each of the nine single digits from the
first three parts if the ISBN (sans the non-numeric hyphens), multiplies
each single digit by a number that is less than eleven the number of
positions from the left each digit that is in the ISBN, adds together the
result of each multiplication plus the check digit, and then divides that
number by eleven. If that division by eleven results in no remainder
(i.e., the number is modulo 11), the candidate ISBN is a valid ISBN. For
example, using the previous sample ISBN 3-88053-002-5:

  ISBN:              3  8  8  0  5  3  0  0  2  5
  Digit Multiplier: 10  9  8  7  6  5  4  3  2  1
  Product:          30+72+64+00+30+15+00+00+04+05 = 220
  
Since 220 is evenly divisible by eleven, this candidate IDBN is a valid
ISBN code.

This verification algorithm is easily translated into Pascal/Delphi code.
String manipulation functions and procedures are used to extract the
check digit and the remainder of the ISBN from the String type value
passed to a validation function. The check digit is converted to Integer
type, which forms the start value of the aggregate variable onto which the
multiplication of each digit in the remainder of the ISBN (the single
digits that comprise the first three parts of the ISBN) will be added. A
For loop is used to sequentially process each digit in the remainder,
ignoring the hyphens, multiplying each digit times its position in the
ISBN remainder relative to the other digits in the remainder. The final
value of this aggregate variable is then checked to see whether it is
evenly divisible by eleven (indicating a valid ISBN) or not (indicating an
invalid candidate ISBN).

Here is an example of this methodology applied in a Delphi function:

function IsISBN(ISBN: String): Boolean;
var
  Number, CheckDigit: String;
  CheckValue, CheckSum, Err: Integer;
  i, Cnt: Word;
begin
  {Get check digit}
  CheckDigit := Copy(ISBN, Length(ISBN), 1);
  {Get rest of ISBN, minus check digit and its hyphen}
  Number := Copy(ISBN, 1, Length(ISBN) - 2);
  {Length of ISBN remainder must be 11 and check digit between 9 and 9 or
   X}
  if (Length(Number) = 11) and (Pos(CheckDigit, '0123456789X') > 0) then
    begin
    {Get numeric value for check digit}
    if (CheckDigit = 'X') then
      CheckSum := 10
    else
      Val(CheckDigit, CheckSum, Err);
    {Iterate through ISBN remainder, applying decode algorithm}
    Cnt := 1;
    for i := 1 to 12 do begin
      {Act only if current character is between "0" and "9" to exclude
       hyphens}
      if (Pos(Number[i], '0123456789') > 0) then begin
        Val(Number[i], CheckValue, Err);
        {Algorithm for each character in ISBN remainder, Cnt is the nth
        character so processed}
        CheckSum := CheckSum + CheckValue * (11 - Cnt);
        Inc(Cnt);
      end;
    end;
    {Verify final value is evenly divisible by 11}
    if (CheckSum MOD 11 = 0) then
      IsISBN := True
    else
      IsISBN := False;
  end
  else
    IsISBN := False;
end;

This is a simplified example, kept simple to best demonstrate the
algorithm to decode ISBNs. There are a number of additional features that
would be desirable to add for use in a real-world application. For
instance, this example function requires the candidate ISBN be passed as a
Pascal String type value, with the hyphens dividing the four parts of the
ISBN. Added functionality might accommodate evaluating candidate ISBNs
entered without the hyphens. Another feature that might be added is
checking that ensures three hyphens are properly included, as opposed to
just thirteen number characters.


Selecting multiple records in TDBGrid

Question

How to select multiple record in TDBGrid?

Answer

When you add [dgMultiSelect] to the Options
property of a DBGrid, you give yourself the ability
to select multiple records within the grid.

The records you select are represented as bookmarks
and are stored in the SelectedRows property.

The SelectedRows property is an object of type
TBookmarkList.  The properties and methods are
described below.

// property SelectedRows: TBookmarkList read FBookmarks;

//   TBookmarkList = class
//   public

     {* The Clear method will free all the selected records
        within the DBGrid *}
     // procedure Clear;

    {* The Delete method will delete all the selected rows
       from the dataset *}
    // procedure Delete;

    {* The Find method determines whether a bookmark is
       in the selected list. *}
    // function  Find(const Item: TBookmarkStr;
    //      var Index: Integer): Boolean;

    {* The IndexOf method returns the index of the
       bookmark within the Items property. *}
    // function IndexOf(const Item: TBookmarkStr): Integer;

    {* The Refresh method returns a boolean value to notify
       whether any orphans were dropped (deleted) during the
       time the record has been selected in the grid.  The
       refresh method can be used to update the selected list
       to minimize the possibility of accessing a deleted
       record. *}
    // function  Refresh: Boolean;  True = orphans found

    {* The Count property returns the number of currently
       selected items in the DBGrid *}
    // property Count: Integer read GetCount;

    {* The CurrentRowSelected property returns a boolean
       value and determines whether the current row is
       selected or not. *}
    // property CurrentRowSelected: Boolean
    //      read GetCurrentRowSelected
    //      write SetCurrentRowSelected;

    {* The Items property is a TStringList of
       TBookmarkStr *}
    // property Items[Index: Integer]: TBookmarkStr
    //      read GetItem; default;

//  end;

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, Grids, DBGrids, DB, DBTables;

type
  TForm1 = class(TForm)
    Table1: TTable;
    DBGrid1: TDBGrid;
    Count: TButton;
    Selected: TButton;
    Clear: TButton;
    Delete: TButton;
    Select: TButton;
    GetBookMark: TButton;
    Find: TButton;
    FreeBookmark: TButton;
    DataSource1: TDataSource;
    procedure CountClick(Sender: TObject);
    procedure SelectedClick(Sender: TObject);
    procedure ClearClick(Sender: TObject);
    procedure DeleteClick(Sender: TObject);
    procedure SelectClick(Sender: TObject);
    procedure GetBookMarkClick(Sender: TObject);
    procedure FindClick(Sender: TObject);
    procedure FreeBookmarkClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  Bookmark1: TBookmark;
  z: Integer;

implementation

{$R *.DFM}

//Example of the Count property
procedure TForm1.CountClick(Sender: TObject);
begin
  if DBgrid1.SelectedRows.Count > 0 then
  begin
    showmessage(inttostr(DBgrid1.SelectedRows.Count));
  end;
end;

//Example of the CurrentRowSelected property
procedure TForm1.SelectedClick(Sender: TObject);
begin
  if DBgrid1.SelectedRows.CurrentRowSelected then
    showmessage('Selected');
end;

//Example of the Clear Method
procedure TForm1.ClearClick(Sender: TObject);
begin
  dbgrid1.SelectedRows.Clear;
end;

//Example of the Delete Method
procedure TForm1.DeleteClick(Sender: TObject);
begin
  DBgrid1.SelectedRows.Delete;
end;

{*
   This example iterates through the selected rows
   of the grid and displays the second field of
   the dataset.

   The Method DisableControls is used so that the
   DBGrid will not update when the dataset is changed.
   The last position of the dataset is saved as
   a TBookmark.

   The IndexOf method is called to check whether or
   not the bookmark is still existent.
   The decision of using the IndexOf method rather
   than the Refresh method should be determined by the
   specific application.
*}

procedure TForm1.SelectClick(Sender: TObject);
var
  x: word;
  TempBookmark: TBookMark;
begin
  DBGrid1.Datasource.Dataset.DisableControls;
  with DBgrid1.SelectedRows do
  if Count > 0 then
  begin
    TempBookmark:= DBGrid1.Datasource.Dataset.GetBookmark;
    for x:= 0 to Count - 1 do
    begin
      if IndexOf(Items[x]) > -1 then
      begin
        DBGrid1.Datasource.Dataset.Bookmark:= Items[x];
        showmessage(DBGrid1.Datasource.Dataset.Fields[1].AsString);
      end;
    end;
  end;
  DBGrid1.Datasource.Dataset.GotoBookmark(TempBookmark);
  DBGrid1.Datasource.Dataset.FreeBookmark(TempBookmark);
  DBGrid1.Datasource.Dataset.EnableControls;
end;

{*
This example allows you to set a bookmark and
and then search for the bookmarked record within
selected a record(s) within the DBGrid.
*}

//Sets a bookmark
procedure TForm1.GetBookMarkClick(Sender: TObject);
begin
  Bookmark1:= DBGrid1.Datasource.Dataset.GetBookmark;
end;

//Frees the bookmark
procedure TForm1.FreeBookmarkClick(Sender: TObject);
begin
  if assigned(Bookmark1) then
  begin
    DBGrid1.Datasource.Dataset.FreeBookmark(Bookmark1);
    Bookmark1:= nil;
  end;
end;

//Uses the Find method to locate the position of the
//bookmarked record within the selected list in the DBGrid
procedure TForm1.FindClick(Sender: TObject);
begin
  if assigned(Bookmark1) then
  begin
    if DBGrid1.SelectedRows.Find(TBookMarkStr(Bookmark1),z) then
      showmessage(inttostr(z));
  end;
end;

end.


Moving in a TMemo Field

Question

I'm working with a TMemo Field, and I insert several lines in it using the
Addlines procedure, with the lines attribute. The problem is that after the
inserting is done, the field looks empty because the cursor is at the bottom
of the field, so I have to scroll up manually in order to be able to see what
has been inserted. My question is: Is there any way to do this scrolling up
automatically? Or in other words, How can I move to the begining of the TMemo
Field?

Answer

A:
procedure MemoCursorTo(Memo:TMemo; MemoLine, MemoCol: Integer);
begin
       Memo.SelStart := SendMessage (Memo.Handle, EM_LINEINDEX, MemoLine, 0)
       + MemoCol - 1;
end;

Copy of component properties

Question

Is there an easy way to copy all the properties of a component into another one
of the same class, when the class is known only at run time?

Answer

Because of D1/D2 VCL internal object semantics and your own additional
semantics and D2 strings, you cannot just make a shallow copy (that
is, a copy the memory contents of the object, as is) of components.
There are two ways to make deeper copies:

(1) If there is an ASSIGN method, that may do what you want.

(2) You can use the same code the VCL uses to copy things to and from
the clipboard. You stream the component into memory and then read it
back in using the provided methods to create a new component. It
enables you to put in a new Owner and Parent, which is typically what
you want to do, and you can specify some hooks to handle other issues.
It is pretty fast.

Using the clipboard technique creates a new instance of the component,
and that may not be what you want if you have already created the
component. In that case, you need to write your own ASSIGN method.

TMenuItem - create and add an event at runtime

Question

I need some help about TMenuItem:
I would like to know how I can create and associate event dynamically to a
TMenuItem.

Answer

You can populate a menu like this:
   			...
      			ppmProgram: TMenuItem;
   			Private
      			procedure PopulateMenu(Sender: TObject);
      			procedure NewShortcutClick(Sender: TObject);
		   ...

		procedure TForm1.PopulateMenu(Sender: TObject);
		var
		   ppmAddNewShortcut : TMenuItem;
		begin
		   ppmAddNewShortcut         := TMenuItem.Create(Self);
		   ppmAddNewShortcut.Caption := '&Test';
		   ppmAddNewShortcut.OnClick := NewShortcutClick;
		   ppmProgram.Add(ppmAddNewShortcut);
		end;

		procedure TForm1.NewShortcutClick(Sender: TObject);
		begin
		   { Type the "Test" code here }
		end;

Scrolling a TRichEdit control

Question

I am using the TRichEdit.lines.add() method to add lines to a TRichEdit.
How do I make it so that each time a line is added, the text scrolls so
that the line just added is at the bottom of the TRichEdit control?

Answer

Just use a EM_SCROLL message:
SendMessage(RichEdit1.Handle, EM_SCROLL, SB_LINEDOWN, 0);

Mask Edit

Question

I need to be able to validate that every thing a person inputs in to an edit
box is a numeric value.  I was using and edit box allowing them to input
numbers then hit the enter key (set to default) on their key boards to enter
the value.

Answer

My suggestion is to set the OnKeyPress event to something like this:

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
  if    NOT (Key in ('0'..'9', '.', #8, #13)) // acceptable keys
     OR (    (Key = '.')                  // user pressed '.'
         AND (POS ('.', Edit1.Text) > 0)) // already has a decimal point
    then
    begin
      Key := #0;
      MessageBeep (MB_OK);
    end;
end;

This checks to see if the key you pressed was a digit, a [Tab] or an
[Enter]. Anything else is not accepted (by setting Key to null) and the
computer beeps.

If you need to don't need to accept a decimal point you can take out the OR
clause in the if statement and the '.' in the first condition. If you want
to check that the entered field in between LowLimit and HighLimit you could
use this:

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
  if    NOT (Key in ('0'..'9', #8, #13))
     OR (StrToInt (Edit1.Text + Key) < LowLimit)
     OR (StrToInt (Edit1.Text + Key) > HighLimit)
    then
    begin
      Key := #0;
      MessageBeep (MB_OK);
    end;
end;

List Box Horizontal Scroll

Question

In D1, for a tListBox, does anyone know how to either
A) Get a horizontal scroll bar to appear when the data within the list box is
too long for box OR failing that
B) Just get a horizontal scroll bar in the thing at all

Answer

Try adding this in the Create event for your listboxes parent form:

{enable horizontal scroll box in example titles list box}
SendMessage(lstInclude.Handle, LB_SETHORIZONTALEXTENT, 1000, LongInt(0));

1000 is just some arbitrarily big number to force a scrollbar. You could
calculate this based on some TextWidth('widest item') calculation

ComboBox dropdown

Question

Is there a way to have a combo box(dropdown list) open it's list by code?

Answer

A:
procedure TForm1.ComboBox1Enter(Sender:TObject);
begin
   SendMessage(ComboBox1.Handle,CB_SHOWDROPDOWN,Integer(True),0);
end;

Link this routine to the OnEnter event of the ComboBox.

A:
SendMessage(combobox1.Handle, CB_SHOWDROPDOWN, 1, 0);
Change the third parameter(1) to 0 if you want to hide the list.

TProgressBar in TStatusPanel

Question

Anyone found a way to put a TProgressBar in a TStatusPanel?

Answer

unit adStatba;

interface

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

type
  TAdrockStatusBar = class(TStatusBar)
  private
    { Private declarations }
  protected
    { Protected declarations }
  public
    { Public declarations }
    Constructor Create(Aowner : TComponent); override;
  published
    { Published declarations }
  end;

procedure Register;

implementation

Constructor TAdrockStatusBar.Create(Aowner : TComponent);
begin
  inherited Create(Aowner);
  ControlStyle               := ControlStyle + [csAcceptsControls];
end;

procedure Register;
begin
  RegisterComponents('Adrock', [TAdrockStatusBar]);
end;

end.

It allows you to put a control into the panel. But it does not support the
sub panels. You might need to align the control yourself over a sub panel
to make it look right. But that is not too hard to do...

TTreeView slow down

Question

When I add about 60 items (& subitems) to a TTreeView component it takes
about 3 seconds.  This seems far too slow, since the windows explorer
can do it almost instantly.

It seems that when I add the items all at once the TTreeView tries to
redraw everytime an item is added (this is my theory!).  Is there a way
to halt any redrawing until I've add all my items?  Or is there somthing
else I should look at?

Answer

A:
Try
  LockWindowUpdate(TreeView1.Handle);
  ...
finally
  LockWindowUpdate(0);


A:
Set the sort property on off.


A:
I've done alot of work with TTreeView. I typically add and manipulate
hundreds, sometimes thousands of nodes at a time. The biggest time-savers
have been:

-Using TreeView1.BeginUpdate and TreeView1.EndUpdate before and after I make
a large number of changes or additions.

-Setting SortType to stNone by default. (Preventing the tree from
automatically sorting items every time they are added or modified.  This
will probably be your biggest time-saver.)

If you need your items to be sorted, you can save time by sorting nodes only
when they are visible.  Since you add items to the tree yourself, you can
choose to sort whatever you make visible by default, then sort children as
they are exposed (their parent is expanded).  I do this by attaching the
following code to the OnExpanded event:

procedure TForm1.TreeView1Expanded(Sender: TObject; Node: TTreeNode);
begin
  Node.Alphasort;  {Sort Node's children and -only- Node's children}
end;

That will take care of sorting for every level except the root level.  To my
knowledge, there is no way to tell a TTreeView to just sort it's root nodes.
TreeView1.Alphasort sorts -every- item in the tree (big time-cruncher).  If
you need to sort items on the root level without sorting every item on the
tree, you'll have to do it yourself.  I'd probably start with QuickSort or
InsertionSort, and the method TTreeNode.MoveTo.


A:
Put your TreeView-Add's between a call to TreeView1.Items.BeginUpdate and
TreeView1.Items.EndUpdate.
Also make sure the treeview is unsorted.









© DelphiRSS.com. All Rights Reserved.