发信人: bbbbbbbbbbbb (刀剑笑), 信区: BorlandDev
标  题: [合集]关于VCL的FAQ -- CF dumpaged 
发信站: 哈工大紫丁香 (2002年12月30日15:46:46 星期一), 站内信件


────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日11:40:41 星期天 说道:

以下文章转至http://www.scalabium.com/faq/
VCL
#0002 How can I draw the rotated text on canvas? 
#0005 To change a font style (bold nodes) in standard TTreeview component 
#0008 To disable a form movement 
#0010 To add the horizontal scrollbar in listbox 
#0012 To set a checkbox without OnClick event 
#0013 To disable the on-fly tooltips in TTreeView 
#0018 The form life cycle after creation 
#0023 To get a line/column number that a memo/richtext cursor is on 
#0025 To use a combobox instead inplace editor in TStringGrid 
#0028 The offsets for TMemo 
#0029 The quick filling a listbox with file names 
#0034 To change alignment for TEdit 
#0035 To make a form like system modal 
#0036 To create a non-rectangular control 
#0038 How to add a flat/hot track effect to components 
#0057 To delete the row in TStringGrid component 
#0065 To save/restore the component into BLOB-field 
#0067 Access to some item of TRadioGroup component 
#0085 To save/load the glyphs to/from external file 
#0094 To assign a values to TStrings instead objects 
#0102 To scroll a text in memo in run-time 
#0146 Standard RichEdit component and URL highlighting 
※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 218.8.78.140]

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日16:59:04 星期天 说道:

------------------------------------------------------------------------
--------
With next procedure you can display the text with any angle. 
procedure AngleTextOut(ACanvas: TCanvas; Angle, X, Y: Integer; Str: 
string);
var
  LogRec: TLogFont;
  OldFontHandle,
  NewFontHandle: hFont;
begin
  GetObject(ACanvas.Font.Handle, SizeOf(LogRec), Addr(LogRec));
  LogRec.lfEscapement := Angle*10;
  NewFontHandle := CreateFontIndirect(LogRec);
  OldFontHandle := SelectObject(ACanvas.Handle, NewFontHandle);
  ACanvas.TextOut(X, Y, Str);
  NewFontHandle := SelectObject(ACanvas.Handle, OldFontHandle);
  DeleteObject(NewFontHandle);
end;
Angle is the angle of rotation in the degrees, clockwise.
If somebody know how display the rotated text with wordwrap property, 
then
send a sources or link to me.
PS: I hope that you not forget that on my site you can download a 
freeware
TAngleLabel component with correct layout and alignment calculations.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日16:59:38 星期天 说道:

#5: Can I display a node in standard TTreeView component with bold style
 without custom drawing?
--------
Time-to-time I receive the question how I realized a selection by the 
bold font for some nodes in the SMReport Explorer form.
Today I have decided to describe this very simple way (but very useful).
 It does not require an override of any custom drawing methods/events, 
creating
a new component etc. It's a real standard way.
**********************************************************
The standard Windows Treeview control have a few state flags 
(TVIS_BOLD and TVIS_CUT in our example), due to which it's possible to 
reach wished.
At first, let's write the procedure SetNodeState:
procedure SetNodeState(node: TTreeNode; Flags: Integer);
var tvi: TTVItem;
begin
  FillChar(tvi, SizeOf(tvi), 0);
  tvi.hItem := node.ItemID;
  tvi.Mask := TVIF_STATE;
  tvi.StateMask := TVIS_BOLD or TVIS_CUT;
  tvi.State := Flags;
  TreeView_SetItem(node.Handle, tvi);
end;
And now we can set a wished flags:
SetNodeState(node, TVIS_BOLD) - to set the node as Bold
SetNodeState(node, TVIS_CUT) - to set the node as Cutted
SetNodeState(node, TVIS_BOLD or TVIS_CUT) - to set the node as Bold 
and
Cutted
SetNodeState(node, 0) - to set a node as normal
*******************************************
PS: tomorrow I'll release a new SMReport Designer and SMReport 
Autogenerated.
In new distribution kit I shall include the new demo
application with sources, in which you can view a SetNodeState procedure
 using too.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:00:17 星期天 说道:

------------------------------------------------------------------------
--------
If you want to add a horizontal scrollbar in TListBox
(which have a vertical scrollbar only by default),
you need send a LB_SETHORIZONTALEXTENT message:
SendMessage(ListBox1.Handle, LB_SETHORIZONTALEXTENT, FMaxItemWidth, 0);
For example, the next code shows how you can scroll a data
with maximum width of item strings:
procedure TForm1.FormCreate(Sender: TObject);
var i, intWidth, intMaxWidth: Integer;
begin
  intMaxWidth := 0;
  for i := 0 to ListBox1.Items.Count-1 do
  begin
    intWidth := ListBox1.Canvas.TextWidth(ListBox1.Items.Strings[i] + 
'x');
    if intMaxWidth < intWidth then
      intMaxWidth := intWidth;
  end;
  SendMessage(ListBox1.Handle, LB_SETHORIZONTALEXTENT, intMaxWidth, 0);
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:00:55 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes it's necessary to change a state of TCheckBox component, but 
without operation of OnClick event. This task can be solved so: 
{set a checked}
yourCheckBox.Perform(BM_SETCHECK, 1, 0);
{set a unchecked}
yourCheckBox.Perform(BM_SETCHECK, 0, 0);
or else in one procedure 
procedure ChangeChecked(ACheckBox: TCheckBox; AState: Boolean);
begin
  ACheckBox.Perform(BM_SETCHECK, Ord(AState), 0);
end;
For example, if you will call the ChangeChecked(myCheckBox, True), 
then will be myCheckBox.Checked = True. 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:01:10 星期天 说道:

------------------------------------------------------------------------
--------
If you have installed the Internet Explorer 4.0 or high, in TTreeView 
component always displaying a hint for cutted items. It's useful but 
sometimes prevents and irritates (at least, me). But there is a simple 
way to switch off this feature: 
const TVS_NOTOOLTIPS = $0080;
begin
  SetWindowLong(yourTreeView.Handle, GWL_STYLE,
         GetWindowLong(yourTreeView.Handle, GWL_STYLE) xor 
TVS_NOTOOLTIPS);
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:01:31 星期天 说道:

------------------------------------------------------------------------
   1. OnCreate
   2. OnShow
   3. OnPaint
   4. OnActivate
   5. OnResize
   6. OnPaint again

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:02:02 星期天 说道:

------------------------------------------------------------------------
--------
If you want to retrieve the line and/or column number you must send a 
messages to control and retrieve the result:
For TRichEdit you must send a EM_EXLINEFROMCHAR and EM_LINEINDEX 
messages.
For TMemo you must send a EM_LINEFROMCHAR and EM_LINEINDEX messages.
The WParam contains the character number that you wish the line number 
for, or -1 for the current line (where the caret is located or the 
beginning of a text selection).
View examples:
- for TMemo
LineNumber := SendMessage(Memo1.Handle, EM_LINEFROMCHAR, -1, 0);
- for TRichEdit
LineNumber  := SendMessage(RichEdit1.Handle, EM_EXLINEFROMCHAR, 0, 
RichEdit1.SelStart);
ColNumber := (RichEdit1.SelStart - SendMessage(RichEdit1.Handle, 
EM_LINEINDEX, LineNumber, 0));
PS: not forget that starting values is 0.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:02:33 星期天 说道:

------------------------------------------------------------------------
--------
I wrote a small app which demonstrates how you can use the some combobox
 instead standard inplace editor in TStringGrid component.
Also in this app you can view how change font and/or alignment for 
some cells.
PS: you can download this app from hotinfo page on my site. The direct 
link:
http://www.scalabium.com/faq/strgrid.zip (120K)

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:02:55 星期天 说道:

------------------------------------------------------------------------
--------
Today I have read in book that for standard TMemo component you can 
set the offsets!
Try it:
var Rect: TRect;
begin
  SendMessage( Memo1.Handle, EM_GETRECT, 0, LongInt(@Rect));
  Rect.Left:= 20;
  SendMessage(Memo1.Handle, EM_SETRECT, 0, LongInt(@Rect));
  Memo1.Refresh;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:03:14 星期天 说道:

------------------------------------------------------------------------
--------
Today I have read, that you can fill a listbox items with file names 
by one message only! Try it - it's cool:
var s: string;
begin
  s := 'c:\windows\*.bmp'#0;
  ListBox1.Perform(LB_DIR, DDL_READWRITE, LongInt(@s[1]));
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:04:09 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes you need change the text alignment in standard TEdit 
component.
For some reason the developers in Microsoft decided, that for data 
editing in single line we do not need to change alignment and haven't 
provided such possibility:(
But sometimes I need it! For example, I like view a numbers with right 
alignment...
If you need it too then this delphi tip for you:
type TEditAlignment = class(TCustomEdit)
  protected
    { Protected declarations }
    procedure CreateParams(var Params: TCreateParams); override;
  end;
procedure TEditAlignment.CreateParams(var Params: TCreateParams);
const Alignments: array[TAlignment] of Longint =
      (ES_LEFT, ES_RIGHT, ES_CENTER);
begin
  inherited CreateParams(Params);
  Params.Style := Params.Style or ES_MULTILINE or
                  Alignments[FAlignment];
end;
In Windows 98 you can set a Params.Style without ES_MULTILINE flag and 
it too will work.
Also after such edit control can't correctly work with PasswordChar <> 
#0 (but I think for password input it's not necessary to change 
alignment).
PS: remark, that after that your TEdit is not "real" edit control - 
now is a control like "memo" but single line... Of course, you can use a
 standard TMemo component with height equal to one line.
PPS: You can download our freeware TTypedEdit 
component(http://www.scalabium.com/edittype.htm) 
with extended possibilities for control of user typing, button in right side of 
edit,  alignment and more

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:04:34 星期天 说道:

------------------------------------------------------------------------
--------
If you need stop any process and wait until your window will be closed 
(set a window system modal), you need call:
SetSystemModalWindow(yourForm.Handle);

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:04:44 星期天 说道:

------------------------------------------------------------------------
--------
If you want to create a non-rectangular control or form, you can 
create a some region and set this region to your wished window control.
For example, write in OnYourFormCreate event:
SetWindowRgn(Edit1.Handle, CreateRoundRectRgn(2, 2, Edit1.Width - 2, 
Edit1.Height - 2, 15, 15), True);
This make the oval edit control.
Also view help for CreateRectRegn, CreateRectRgnIndirect API's 
functions.
You can set a region to any window control (to change a form shape, edit
 shape etc) - use a YourControl.Handle instead Edit1.Handle in the 
example.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:05:43 星期天 说道:

------------------------------------------------------------------------
--------
If you want to add a useful feature to your component (like URL in 
html or PageControl/TabControl.HotTrack) you must handle the 
CM_MOUSEENTER and CM_MOUSELEAVE messages:
type
  TyourControl = class(TDescControl)
  private
    { Private declarations }
    FLinkFont: TFont;
    FPassiveFont: TFont;
    procedure CMMouseEnter(var Msg: TMessage); message CM_MOUSEENTER;
    procedure CMMouseLeave(var Msg: TMessage); message CM_MOUSELEAVE;
  end;
implementation
procedure TyourControl.CMMouseEnter(var Msg: TMessage);
begin
 //Change color when mouse is over control
 Font.Assign(FLinkFont);
end;
procedure TyourControl.CMMouseLeave(var Msg: TMessage);
begin
 //Change color when mouse leaves control
 Font.Assign(FPassiveFont);
end;
As example, you can view a sources of the 
TURLLabel(http://www.scalabium.com/urllbl.htm) 
 and/or 
THighLightLabel(http://www.scalabium.com/hllbl.htm) 
components on our site.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:06:35 星期天 说道:

------------------------------------------------------------------------
--------
If you worked with TStringGrid component, then you saw that in this 
component the Borland developers not provided the method for row 
deleting. In this tip I describe the few ways for it.
1. navigate by rows and copy the row contains to the prev row:
procedure DeleteRow(yourStringGrid: TStringGrid; ARow: Integer);
var i, j: Integer;
begin
  with yourStringGrid do
  begin
    for i := ARow to RowCount-2 do
      for j := 0 to ColCount-1 do
        Cells[j, i] := Cells[j, i+1];
    RowCount := RowCount - 1
  end;
end;
2. the modificated #1:
procedure DeleteRow(yourStringGrid: TStringGrid; ARow: Integer);
var i: Integer;
begin
  with yourStringGrid do
  begin
    for i := ARow to RowCount-2 do
      Rows[i].Assign(Rows[i+1]);
    RowCount := RowCount - 1
  end;
end;
3. the "hacked" way. The TCustomGrid type (the TStringGrid is 
TCustomGrid's successor) have the DeleteRow method. But this method 
allocated not in public section but in protected section. So the all 
successors can "see" this DeleteRow method.
type
  THackStringGrid = class(TStringGrid);
procedure DeleteRow(yourStringGrid: TStringGrid; ARow: Integer);
begin
  with THackStringGrid(yourStringGrid) do
    DeleteRow(ARow);
end;
Personally I use the third method but the first and second are more 
visual.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:06:48 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes in applications you must save the component into BLOB-field of
 the own table and in run-time to restore it. For example, to save the 
report form and in run-time the end-user will select the wished report 
before report generation.
The next two procedure allows to save the component with all 
properties and restore it.
procedure SaveCompToBlob(AField: TBlobField; AComponent: TComponent);
var
  Stream: TBlobStream;
  CompName: string;
begin
  CompName := Copy(AComponent.ClassName, 2, 99);
  Stream := TBlobStream.Create(AField, bmWrite);
  try
    Stream.WriteComponentRes(CompName, AComponent);
  finally
    Stream.Free;
  end;
end;
procedure LoadCompFromBlob(AField: TBlobField; AComponent: TComponent);
var
  Stream: TBlobStream;
  i: integer;
begin
  try
    Stream := TBlobStream.Create(AField, bmRead);
    try
      {delete the all child components}
      for i := AComponent.ComponentCount - 1 downto 0 do
        AComponent.Components[i].Free;
      Stream.ReadComponentRes(AComponent);
    finally
      Stream.Free;
    end;
  except
    on EFOpenError do {nothing};
  end;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:07:18 星期天 说道:

------------------------------------------------------------------------
--------
If you want to change a some property for item in TRadioGroup, you can 
do it:
procedure TForm1.Button1Click(Sender: TObject);
begin
  TRadioButton(RadioGroup1.Controls[1]).Enabled := False;
end;
Of course, you can change the any property for each item.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:07:30 星期天 说道:

------------------------------------------------------------------------
--------
In some small programms sometimes very useful to store a glyphs in 
some external file and in run-time to load these glyphs in own 
TImageList. Also if the user can change the images, then in 
application closing you can save the TImageList contents into this 
external file.
For example, now I develop the small aplication for password keeper 
but I don't want to have the any database engine (BDE, ADO etc). I use 
the own file format for password storing. Each password record have 
the category field. For example, the Provider login, Bank secure code, 
Credit Card PIN, Serial Number of application etc
Of course, very useful to assign the some glyph to each category and 
show this glyph in password list instead full category name.
I use the next code:
procedure SaveGlyphs(FileName: string; lstImages: TImageList);
begin
  with TFileStream.Create(FileName, fmCreate or fmShareExclusive) do
    try
      WriteComponent(lstImages);
    finally
      Free;
    end;
end;
The similar code you can use for glyphs loading:
procedure ReadGlyphs(FileName: string; lstImages: TImageList);
begin
  with TFileStream.Create(FileName, fmOpenRead) do
    try
      ReadComponent(lstImages);
    finally
      Free;
    end;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:07:47 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes in development you must use the some values (for example, 
integer or strings) as data to each item in list (TStrings).
For example, you have the listbox with country name but you want to link
 a code value for each item. Or other example: you have a combobox 
with colors and you needs to link a color value to each colored line.
These tasks can be easy solved - the TStrings type have:
- the Items property: the string items which will be displayed in 
combobox or listbox
- the Objects property: the data which can be linked to each visible 
item.
Each Objects[i] is the pointer to some external data.
In this tip I want to explain how you can use this Objects property 
for storing of some additional info.
1. to store the integer values:
with qryGroups do
begin
  First;
  while not Eof do
  begin
    cbGroups.Items.AddObject(FieldByName('Name').AsString,
                             TObject(FieldByName('ID').AsInteger));
    Next;
  end;
end;
to read the integer value from selected item:
strID := LongInt(cbGroups.Items.Objects[cbGroups.ItemIndex]);
Comments: in this example, I used the type convertion - each pointer 
is address. But address is an integer value so we can place to item data
 the "virtual" address.
2. to store the string values:
with qryGroups do
begin
  First;
  while not Eof do
  begin
    cbGroups.Items.AddObject(FieldByName('Name').AsString,
                   TObject(LongInt(NewStr(FieldByName('ID').
AsString))));
    Next;
  end;
end;
to read a value from selected item:
strID := PString(cbGroups.Items.Objects[cbGroups.ItemIndex])^;
Comments: in this example I used the address on string which I created 
(see a help topic on NewStr).
Also don't forget that you must destroy the objects data which you 
created in this example. For exampe, you can do it in OnDestroy event:
for i := 0 to cbGroups.Items.Count-1 do
  DisposeStr(PString(cbGroups.Items.Objects[i]));

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:08:01 星期天 说道:

------------------------------------------------------------------------
--------
In few tasks I needs to add a some strings in end of TMemo in run-time 
but after that I must show a first strings. For example, it's very 
useful in logging system when you inserted a lot of status messages 
but user must view a first lines.
This task can be solved in two lines of code:
  <.. to add a lines ..>
  {set a current position of cursor to start}
  SendMessage(Memo1.Handle, EM_SETSEL, 0, 0);
  {scroll a memo to start position}
  SendMessage(Memo1.Handle, EM_SCROLLCARET, 0, 0);
As alternative method you can set a Memo1.Visible := False before 
insertion of new strings and after that to set a Memo1.Visible := True.
 You'll receive a same result.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:08:32 星期天 说道:

------------------------------------------------------------------------
--------
Very popular question in delphi forums is: how can I highlight URLs in 
RichEdit and how can I detect a mouse click in text where URL is...
And everytime I see the answers like "go to XXXX site and use this 
superb XXX product instead RichEdit".
Today I want to show how to implement URL highlighting and URL 
navigation without any third-party components. This functionality is 
implemented in RichEdit from Microsoft (and MS Outlook use this feature,
 for example) and only Borland's developers didn't publish it for us.
So what we need:
1. drop on your form a RichEdit component from win32 page of component 
palette
2. in OnCreate event of your form write the next code:
var
  mask: Integer;
begin
  mask := SendMessage(RichEdit1.Handle, EM_GETEVENTMASK, 0, 0);
  SendMessage(RichEdit1.Handle, EM_SETEVENTMASK, 0, mask or ENM_LINK);
  SendMessage(RichEdit1.Handle, EM_AUTOURLDETECT, Integer(True), 0);
  RichEdit1.Text := 'Scalabium Software'#13#10 +
    ' Site is located at www.scalabium.com. Welcome to our site.';
end;
After that your Richedit will convert automatically any URLs in 
highlighted (blue color and underlined). Even if you'll start to enter 
any text directly in Richedit, any begings for URL will be converted too
 (not only existing text string but new too)
3. now we must detect mouse clicks in URL range. For this task we must 
override WndProc method of our form:
type
  TForm1 = class(TForm)
  protected
    procedure WndProc(var Message: TMessage); override;
  end;
...
procedure TForm1.WndProc(var Message: TMessage);
var
  p: TENLink;
  strURL: string;
begin
  if (Message.Msg = WM_NOTIFY) then
  begin
    if (PNMHDR(Message.LParam).code = EN_LINK) then
    begin
      p := TENLink(Pointer(TWMNotify(Message).NMHdr)^);
      if (p.msg = WM_LBUTTONDOWN) then
      begin
        SendMessage(RichEdit1.Handle, EM_EXSETSEL, 0, LongInt(@(p.
chrg)));
        strURL := RichEdit1.SelText;
        ShellExecute(Handle, 'open', PChar(strURL), 0, 0, 
SW_SHOWNORMAL);
      end
    end
  end;
  inherited;
end;
Now you can compile your project (don't forget to include Richedit and 
ShellAPI units in uses clause) and your RichEdit component will work 
like a sharm.
Of course, you can modify a code and process this parsed strURL as you 
like instead implemented navigation in browser as I did...

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:10:01 星期天 说道:

Databases
#0021 Database programming technology (Design Patterns) 
#0043 To navigate by selected rows in DBGrid 
#0062 To get the alias list, tables list etc 
#0063 To copy (to dublicate) the dataset record 
#0065 To save/restore the component into BLOB-field 
#0079 To export a dataset into XML-file 
#0081 To check if BDE exists 
#0091 To play a wav-file from memory or BLOB-field 
#0092 To switch a DBGrid in edit mode and set a cursor in some position 
#0108 To generate the script for table creation 
#0109 To generate the script for SELECT-statement 
#0111 To create table in MS Access with DAO 
#0141 To save file in BLOB and restore later 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:10:32 星期天 说道:

------------------------------------------------------------------------
--------
Today I answer in Delphi-DB mailing list about subj. I remember, that 
few weeks ago we too wanted to discuss this theme. Also I decided to 
post this message here too. Let it will be by today's delphi tip.
********************************************
On server database I have a few own "system" tables with descriptions of
 project:
1. CLASS table is a list of all available user queries with short 
description, select/update/insert/delete statements
2. DESCRIPTION table stores the descriptions of fields for each record 
in CLASS
3. a few additional tables with security (user, groups, privileges, 
access modes etc)
PS I do not describe them, as for the basic description they are not 
so important
In application I have a main datamodule, which contains the one 
TDatabase component (linked to my alias).
Also I have a some TDataModule component which are the parent for each 
other. On this component I dropped the TQuery (linked to main 
TDataBase), and TUpdateSQL and TDataSource, which are linked to TQuery.
Also in this parent datamodule I included the some additional properties
 and procedures/functions:
1. to get a descriptions from server by class name
2. to fill a update/delete/insert properties of TUpdateSQL
3. to change a filter (via where clause), a data order (via order by) 
etc (any clause in sql - I use a some macros in own sqls)
4. save/cancel changes, refresh data
5. to get the new ID (for autoincremental fields)
6. to read a user privileges
7. to open class
8. to get a "lookup" class
PS: I not use a delphi lookup fields. Instead it I store in class 
descriptions for some fields the additional info (which class I must 
open for edit/view of data, which fields are linked in "lookup" class 
etc and more)
9. and more additional features
Each datamodule are successor of this parent DM with "personal" 
extension in business logic. For example, in the class for details of 
orders I defined the specific calculations (some subtotals and sums) 
or procedures (check of outputing qnt). The full logic I included in 
these DMs only (in visual forms I use a calls only!).
Also I have a basic visual TForm with grid, navigator, filter panel 
etc.
This form have a "linked" DataModule. Also this form known how:
1. to fill the info from DM (a columns list for dbgrid (from description
 in DM), form caption and more)
2. to call a form for edit/append of record (those form not uses the any
 DB-components!!)
3. to generate report (I use a SMReport)
4. to export data (I use a SMExport)
5. to find a record by some criteria
6. to select a records (the some forms I use for selecting of some 
recors)
PS: for example, in orders mode my users can select a records from 
product class and append the wished
7. to open a "lookup" class
PS: for example, in grid with customer list my users can open a class of
 customer types (instead standard delphi lookup field) or "drill" in 
orders list for this customer
8. to setup the visualization of data (fonts, colors, order etc, filter,
 sort and grouping)
9. and more additional features
All the rest forms are successors of this parent and are different one 
from other in needed possibilities only. For example, if my users want 
to calculate a sum of some order for specific product type only, of 
course, I add a calc function in linked DM and drop a button on form for
 calling of this function in DM
PS: in visual forms (when I need to calc or to do a something) I call 
the "assigned" method in "assigned" DM.
Also I have a two list - opened DMs and created forms. So if I need open
 a "lookup" class, I search it in list of DM and create only if I not 
found. And with forms I work similar.
PS: this technology I uses in tens projects (current and finished), in 
small app and in big projects. I described the customers-orders-products
 schema for availability of understanding only. Of course, sometimes 
(more often in the beginning of development of small app) the 
perspective of the extension of functionality up to large 
client-server system (and especially multi-tier) is not visible and it's
 possible to go on easy way - drop on form the ttables (or even to 
allocate them in separate datamodule), linked it with grids, use a 
master-detail link and lookup fields etc
But when you decide to expand possibilities of the app and transfer it 
from local DB (Paradox/DBase/Access etc) on normal DB server and maybe 
use a 3-tier you will understand, that is necessary to change the 
approach to DB programming for rise of productivity, easy support and 
extension of project.
Of course, it's my opinion only, but I have come to such technology by 
many cuts and tries during 10 years on different DBs and tools of 
development. Though I still do not have also thirty years I have a large
 number of successful developments in other departs and if I can to 
someone reduce this long way, it will be well.
I do not apply for indisputable true and I know many weak places in 
the described technology, but they not impasses - it is simply not up to
 the end are realized (and it's good:)))
I shall read criticism of other with pleasure too.
PSS: I'm sorry for long message. I shall be in vacation for Monday (from
 6 to 20 September) and if someone will want to ask me (or simply to 
write a message), send it not only in list but duplicate it on my e-mail
 too, please. I shall be on work today and tomorrow only, but after 
vacation I shall answer all messages (if I not answer before vacation 
shall have time).
********************************************

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:11:03 星期天 说道:

------------------------------------------------------------------------
--------
If your users can select a rows in the DBGrid, then you must navigate 
and process them. The TDBGrid component have a SelectedRows property, in
 which stores a list with bookmarks on each selected record.
var i: Integer;
begin
  for i := 0 to yourMDBGrid.SelectedRows.Count-1 do
  begin
    yourMDBGrid.DataSource.DataSet.Bookmark := yourMDBGrid.
SelectedRows[i];
    {here you can process a selected record.
     For example, to sum the Amount field values}
    Inc(intSumAmount, yourMDBGrid.DataSource.DataSet.
FieldByName('Amount').AsFloat);
  end;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:11:30 星期天 说道:

------------------------------------------------------------------------
--------
If you need show in your application the available alias list or 
available table list for some alias, you must use the Session variable.
This variable have a lot of useful methods.
For example, if you have the cbAliasNames component (combobox for 
alias names), then call:
Session.GetAliasNames(cbAliasnames.Items)
and the items of this combobox will contain the available BDE aliases.
Second example: if you have the cbTablesNames component (combobox for 
tables names for some alias), then call:
Session.GetTableNames(strAliasName, cbAliasnames.Items, '', true, False,
 cbTablesNames)
and the items of this combobox will contain the available table list for
 strAliasName alias. In the second parameter you can specify the 
wildcard symbols. Also you can retrieve the data tables only or data and
 system tables (view fourth parameter).
The GetAliasParams method allows to retrieve the parameters associated 
with wished BDE alias. For example, if you want to read the default user
 name and database folder parameters, you must use the next code:
Session.GetAliasParams(strAliasName, cbAliasParams.Items);
edUserName.Text := cbAliasParams.Items.Values['USER NAME'];
edDirectory.Text := cbAliasParams.Items.Values['PATH'];
Also you can retrieve a list of the stored procedures for some 
database (view the GetStoredProcNames method of Session variable). 
If you need retrieve a list of available BDE drivers only, you must call
 the GetDriverNames method. To retrieve the parameters of some driver, 
you must call the GetDriverParams methods.
The GetIndexNames method of the TTable component allows to retrieve a 
list of all available indexes for a table:
tbl.GetIndexNames(cbIndexNames.Items)

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:11:48 星期天 说道:

------------------------------------------------------------------------
--------
In some apllications, in which user must add a lot of the records in the
 dataset, very useful the "copy record" mode.
For example, the operator added the new record and must add the new, but
 this second record is "similar" to first and only few attributes are 
different. In this case, I use the possibility to dublicate the values 
of the wished record, insert a new record and open the dialog for 
editing these values.
Those from you, who uses the my freeware TSMDBNavigator component, saw 
the new additional Copy button with the next handler:
procedure TSMDBNavigator.CopyRecord;
var varCopyData: Variant;
    i: Integer;
begin
  with DataSource.DataSet do
  begin
    varCopyData := VarArrayCreate([0, FieldCount-1], varVariant);
    for i := 0 to FieldCount-1 do
      varCopyData[i] := Fields[i].Value;
    Insert;
    for i := 0 to FieldCount-1 do
      Fields[i].Value := varCopyData[i];
  end;
end;
So you can assign to the TSMDBNavigator.OnEditRecord event the dialog 
opening for record edit. In this event I pass the second parameter: is 
copy mode or edit mode only.
Of course, you can cut this procedure and use this code for run-time 
record dublication without using TSMDBNavigator component. Also you 
can modify it and exclude the wished fields (for example, 
autoincremental fields). 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:12:23 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes in applications you must save the component into BLOB-field of
 the own table and in run-time to restore it. For example, to save the 
report form and in run-time the end-user will select the wished report 
before report generation.
The next two procedure allows to save the component with all 
properties and restore it.
procedure SaveCompToBlob(AField: TBlobField; AComponent: TComponent);
var
  Stream: TBlobStream;
  CompName: string;
begin
  CompName := Copy(AComponent.ClassName, 2, 99);
  Stream := TBlobStream.Create(AField, bmWrite);
  try
    Stream.WriteComponentRes(CompName, AComponent);
  finally
    Stream.Free;
  end;
end;
procedure LoadCompFromBlob(AField: TBlobField; AComponent: TComponent);
var
  Stream: TBlobStream;
  i: integer;
begin
  try
    Stream := TBlobStream.Create(AField, bmRead);
    try
      {delete the all child components}
      for i := AComponent.ComponentCount - 1 downto 0 do
        AComponent.Components[i].Free;
      Stream.ReadComponentRes(AComponent);
    finally
      Stream.Free;
    end;
  except
    on EFOpenError do {nothing};
  end;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:12:43 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes in our development we must export a data from dataset into 
different formats like MS Excel, Word, HTML, Text etc. Now in the 
Internet we have a new popular format - XML-file. So for large part of 
applications we wants to include the possibility of export into XML, 
of course. I want to demonstrate the sample of one procedure for 
exporting of dataset's data into XML:
procedure DatasetToXML(Dataset: TDataset; FileName: string);
The first Dataset parameter is source dataset with data (your Table or 
Query component, or some other third-party dataset). The second FileName
 parameter is a name of target XML-file. 
{ SMExport suite's free demo
  Data export from dataset into XML-file
  Copyright(C) 2000, written by Scalabium, Shkolnik Mike
  E-Mail:  smexport@scalabium.com
           mshkolnik@yahoo.com
  WEB: http://www.scalabium.com
       http://www.geocities.com/mshkolnik
}
unit DS2XML;
interface
uses
  Classes, DB;
procedure DatasetToXML(Dataset: TDataset; FileName: string);
implementation
uses
  SysUtils;
var
  SourceBuffer: PChar;
procedure WriteString(Stream: TFileStream; s: string);
begin
  StrPCopy(SourceBuffer, s);
  Stream.Write(SourceBuffer[0], StrLen(SourceBuffer));
end;
procedure WriteFileBegin(Stream: TFileStream; Dataset: TDataset);
  function XMLFieldType(fld: TField): string;
  begin
    case fld.DataType of
      ftString: Result := '"string" WIDTH="' + IntToStr(fld.Size) + 
'"';
      ftSmallint: Result := '"i4"'; //??
      ftInteger: Result := '"i4"';
      ftWord: Result := '"i4"'; //??
      ftBoolean: Result := '"boolean"';
      ftAutoInc: Result := '"i4" SUBTYPE="Autoinc"';
      ftFloat: Result := '"r8"';
      ftCurrency: Result := '"r8" SUBTYPE="Money"';
      ftBCD: Result := '"r8"'; //??
      ftDate: Result := '"date"';
      ftTime: Result := '"time"'; //??
      ftDateTime: Result := '"datetime"';
    else
    end;
    if fld.Required then
      Result := Result + ' required="true"';
    if fld.Readonly then
      Result := Result + ' readonly="true"';
  end;
var
  i: Integer;
begin
  WriteString(Stream, '<?xml version="1.0" standalone="yes"?><!-- 
Generated by SMExport -->  ' +
                      '<DATAPACKET Version="2.0">');
  WriteString(Stream, '<METADATA><FIELDS>');
  {write th metadata}
  with Dataset do
    for i := 0 to FieldCount-1 do
    begin
      WriteString(Stream, '<FIELD attrname="' +
                          Fields[i].FieldName +
                          '" fieldtype=' +
                          XMLFieldType(Fields[i]) +
                          '/>');
    end;
  WriteString(Stream, '</FIELDS>');
  WriteString(Stream, '<PARAMS DEFAULT_ORDER="1" PRIMARY_KEY="1" 
LCID="1033"/>');
  WriteString(Stream, '</METADATA><ROWDATA>');
end;
procedure WriteFileEnd(Stream: TFileStream);
begin
  WriteString(Stream, '</ROWDATA></DATAPACKET>');
end;
procedure WriteRowStart(Stream: TFileStream; IsAddedTitle: Boolean);
begin
  if not IsAddedTitle then
    WriteString(Stream, '<ROW');
end;
procedure WriteRowEnd(Stream: TFileStream; IsAddedTitle: Boolean);
begin
  if not IsAddedTitle then
    WriteString(Stream, '/>');
end;
procedure WriteData(Stream: TFileStream; fld: TField; AString: 
ShortString);
begin
  if Assigned(fld) and (AString <> '') then
    WriteString(Stream, ' ' + fld.FieldName + '="' + AString + '"');
end;
function GetFieldStr(Field: TField): string;
  function GetDig(i, j: Word): string;
  begin
    Result := IntToStr(i);
    while (Length(Result) < j) do
      Result := '0' + Result;
  end;
var Hour, Min, Sec, MSec: Word;
begin
  case Field.DataType of
    ftBoolean: Result := UpperCase(Field.AsString);
    ftDate: Result := FormatDateTime('yyyymmdd', Field.AsDateTime);
    ftTime: Result := FormatDateTime('hhnnss', Field.AsDateTime);
    ftDateTime: begin
                  Result := FormatDateTime('yyyymmdd', Field.
AsDateTime);
                  DecodeTime(Field.AsDateTime, Hour, Min, Sec, MSec);
                  if (Hour <> 0) or (Min <> 0) or (Sec <> 0) or (MSec <>
 0) then
                    Result := Result + 'T' + GetDig(Hour, 2) + ':' + 
GetDig(Min, 2) + ':' + GetDig(Sec, 2) + GetDig(MSec, 3);
                end;
  else
    Result := Field.AsString;
  end;
end;
procedure DatasetToXML(Dataset: TDataset; FileName: string);
var
  Stream: TFileStream;
  bkmark: TBookmark;
  i: Integer;
begin
  Stream := TFileStream.Create(FileName, fmCreate);
  SourceBuffer := StrAlloc(1024);
  WriteFileBegin(Stream, Dataset);
  with DataSet do
  begin
    DisableControls;
    bkmark := GetBookmark;
    First;
    {write a title row}
    WriteRowStart(Stream, True);
    for i := 0 to FieldCount-1 do
      WriteData(Stream, nil, Fields[i].DisplayLabel);
    {write the end of row}
    WriteRowEnd(Stream, True);
    while (not EOF) do
    begin
      WriteRowStart(Stream, False);
      for i := 0 to FieldCount-1 do
        WriteData(Stream, Fields[i], GetFieldStr(Fields[i]));
      {write the end of row}
      WriteRowEnd(Stream, False);
      Next;
    end;
    GotoBookmark(bkmark);
    EnableControls;
  end;
  WriteFileEnd(Stream);
  Stream.Free;
  StrDispose(SourceBuffer);
end;
end.
PS: I created this simple procedure from own TSMExportToXML component 
which have a lot of other useful extensions in export process. This 
component is part of shareware SMExport suite for Delphi/C++Builder so I
 cutted a part of code and created a simple procedure for exporting into
 XML.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:12:57 星期天 说道:

------------------------------------------------------------------------
--------
you can check it in two methods:
1. read the registry key:
 RootKey := HKEY_LOCAL_MACHINE;
 OpenKey('SOFTWARE\Borland\Database Engine', False);
 try
   s := ReadString('CONFIGFILE01');
   //BDE installed
 finally
   CloseKey;
 end;
2. you can try to initialize the BDE
  IsBDEExist := Check(dbiInit(nil))
PS: I prefer the second because the some no-good un-installators may 
remove the BDE-file but forget to remove the key in registry:) 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:13:11 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes you needs to play a some sounds from own app but store these 
sounds in BLOB-field of table or in some external files.
To play a wav-file you must call the PlaySound procedure which have 
the few parameters - sound (which you want to play), handle of file with
 resource (only for wav from exe/dll-file) and flags for playing.
For example, if you want to play a wav from BLOB, you can use the next 
code:
var
  b: TBlobStream;
  m: TMemoryStream;
begin
  b := TBlobStream.Create(yourTable, bmRead);
  try
    m := TMemoryStream.Create;
    try
      {copy a wav from BLOB into memory}
      m.CopyFrom(b, b.Size);
      {Play it but if we'll have an error, raise exception}
      Win32Check(PlaySound(m.Memory, 0, SND_SYNC or SND_MEMORY));
    finally
      m.Free
    end;
  finally
    b.Free
  end;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:13:27 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes in development you must control the edit mode of your DBGrid 
and, for example, to place a cursor in some position for user typing. 
You can view the next code, which show how you can solve this task:
procedure TForm1.Button1Click(Sender: TObject);
var
  h: THandle;
begin
  {set a focus to DBGrid}
  DbGrid1.SetFocus;
  {switch to edit mode}
  DbGrid1.EditorMode := True;
  {receive the handle of current window with edit}
  h := Windows.GetFocus;
  {send the EM_SETSEL.
   You must include the two parameters: the start position of cursor and
 end
   position of selection. In the next example, I set a cursor in 
position of
   third character without any selection}
  SendMessage(h, EM_SETSEL, 3, 3);
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:13:40 星期天 说道:

------------------------------------------------------------------------
--------
As you know in DIM: Database Information Manager I realized the few 
typical autogenerated scripts.
For example, for some dataset to generate a standard SELECT-statement or
 INSERT-statement.
Today I want to post a sample code which allows to generate the CREATE 
TABLE-stetement.
function GetCreateTable(Dataset: TDataSet): TStrings;
var
  i: Integer;
  str: string;
begin
  Result := TStringList.Create;
  try
    for i := 0 to DataSet.FieldCount-1 do
    begin
      with DataSet.Fields[i] do
      begin
        str := '   ' + DataSet.Fields[i].FieldName + ' ' +
GetFieldTypeName(DataType);
        if DataType = ftString then
          str := str + '(' + IntToStr(Size) + ')';
        if Required then
          str := str + ' NOT';
        str := str + ' NULL';
      end;
      if (i <> DataSet.FieldCount-1) then
        str := str + ',';
      Result.Add(str);
    end;
  except
    Result.Free;
    Result := nil;
  end;
end;
where GetFieldTypeName is
function GetFieldTypeName(AType: TFieldType): string;
const
  FieldTypes: array [TFieldType] of PChar =
    ('?Unknown?', 'Char', 'Smallint', 'Integer', 'Word', 'Boolean',
     'Float', 'Currency', 'BCD', 'Date', 'Time', 'DateTime',
     'Bytes', 'VarBytes', 'AutoInc', 'Blob', 'Memo', 'Graphic',
     'Blob', 'Blob', 'Blob', 'Blob', 'Cursor',
     'FixedChar', 'WideString', 'Largeint', 'ADT', 'Array', 
'Reference',
     'DataSet', 'OraBlob', 'OraClob', 'Variant', 'Interface',
     'IDispatch', 'Guid'
     );
begin
  if AType < Low(FieldTypes) then
    AType := Low(FieldTypes)
  else
    if AType > High(FieldTypes) then
      AType := Low(FieldTypes);
  Result := UpperCase(StrPas(FieldTypes[AType]));
end;
Few important notes:
1. this code uses the Delphi 5/C++Builder 5 declaration of TFieldType 
type. If you use the other version, you must change the declaration of 
FieldTypes array
2. syntax of CREATE TABLE-statement depends from database type. So you 
must modify it for own needs. You must do it in values of FieldTypes 
array items. For example, client-server databases haven't in-build 
logical type, so you must declare that CHAR(1) is boolean. Or to use the
 MONEY syntax for Currency fields in MS SQL.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:13:52 星期天 说道:

------------------------------------------------------------------------
--------
I know that you have a fan holiday (a Merry Christmas to all!) but I 
work today so serie of tips for SQL-script autogeneration is continue..
.
Today I want to publish a small procedure that generate a 
SELECT-statement for data of table. This code I uses in DIM: Database 
Information Manager:
function GetSelectTable(Dataset: TTable): TStrings;
var
  i: Integer;
  str: string;
begin
  Result := TStringList.Create;
  Result.Add('SELECT');
  try
    for i := 0 to DataSet.FieldCount-1 do
    begin
      if i = 0 then
        str := 'SELECT'
      else
        str := ',';
      str := str + ' ' + DataSet.Fields[i].FieldName;
      Result.Add(str);
    end;
    Result.Add('FROM ' + DataSet.TableName)
  except
    Result.Free;
    Result := nil;
  end;
end;
Of course, you can add the ORDER BY-clause (just iterate by index 
fields) and/or GROUP BY-clause...

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:14:25 星期天 说道:

------------------------------------------------------------------------
--------
In this tip I want to describe how you can in run-time create a table in
 MS Access database using DAO.
I used the next method in SMExport suite:
1. declare the variables:
var
  access, db, td, recordset: Variant;
2. declare the array of consts with data type mappings
(between Delphi field types and DAO field types)
  arrMDBTypes: array[TFieldType] of Integer =
    ({dbText} 10 {ftUnknown},
     {dbText} 10 {ftString},
     {dbInteger} 3 {ftSmallint},
     {dbLong} 4 {ftInteger},
     {dbInteger} 3 {ftWord},
     {dbBoolean} 1 {ftBoolean},
     {dbDouble} 7 {ftFloat},
     {dbCurrency} 5 {ftCurrency},
     {dbDouble} 7 {ftBCD},
     {dbDate} 8 {ftDate},
     {dbTime} 22 {ftTime},
     {dbDate} 8 {ftDateTime},
     {dbLongBinary} 11 {ftBytes},
     {dbLongBinary} 11 {ftVarBytes},
     {dbInteger} 3 {ftAutoInc},
     {dbLongBinary} 11 {ftBlob},
     {dbMemo} 12 {ftMemo},
     {dbLongBinary} 11 {ftGraphic},
     {dbMemo} 12 {ftFmtMemo},
     {dbLongBinary} 11 {ftParadoxOle},
     {dbLongBinary} 11 {ftDBaseOle},
     {dbBinary} 9 {ftTypedBinary},
     {dbText} 10 {ftCursor}
    {$IFDEF VER120}
     ,
     {dbText} 10 {ftFixedChar},
     {dbText} 10 {ftWideString},
     {dbBigInt} 16 {ftLargeint},
     {dbText} 10 {ftADT},
     {dbText} 10 {ftArray},
     {dbText} 10 {ftReference},
     {dbText} 10 {ftDataSet}
    {$ELSE}
    {$IFDEF VER125}
     ,
     {dbText} 10 {ftFixedChar},
     {dbText} 10 {ftWideString},
     {dbBigInt} 16 {ftLargeint},
     {dbText} 10 {ftADT},
     {dbText} 10 {ftArray},
     {dbText} 10 {ftReference},
     {dbText} 10 {ftDataSet}
    {$ELSE}
    {$IFDEF VER130}
     ,
     {dbText} 10 {ftFixedChar},
     {dbText} 10 {ftWideString},
     {dbBigInt} 16 {ftLargeint},
     {dbText} 10 {ftADT},
     {dbText} 10 {ftArray},
     {dbText} 10 {ftReference},
     {dbText} 10 {ftDataSet},
     {dbLongBinary} 11 {ftOraBlob},
     {dbLongBinary} 11 {ftOraClob},
     {dbText} 10 {ftVariant},
     {dbText} 10 {ftInterface},
     {dbText} 10 {ftIDispatch},
     {dbGUID} 15 {ftGuid}
    {$ENDIF}
    {$ENDIF}
    {$ENDIF}
    );
3. load a DAO:
    try
      access := GetActiveOleObject('DAO.DBEngine.35');
    except
      access := CreateOleObject('DAO.DBEngine.35');
    end;
4. open a database
    try
      db := access.OpenDatabase(yourDatabaseName);
    except
      exit
    end;
5. create a new table in opened database
    td := db.CreateTableDef(yourTableName, 0, '', '');
6. add a field descriptions in table
    td.Fields.Append(td.CreateField(strFieldName, 
arrMDBTypes[intDataType], Size));
for example,
    td.Fields.Append(td.CreateField('ID', arrMDBTypes[intDataType], 
Size));
    td.Fields.Append(td.CreateField('NAME', arrMDBTypes[intDataType], 
Size));
7. add a table definition in table list
    db.TableDefs.Append(td);
8. open the created table in database
    recordset := db.OpenTable(yourTableName, 0);
9. append the new record in opened table
    recordset.AddNew;
10. change the field values
     curField := recordset.Fields[0].Value := 1;
     curField := recordset.Fields[1].Value := 'First record';
11. post the new record
     recordset.Update(dbUpdateRegular, False);
where
const
  dbUpdateRegular = 1;
12. close a recordset
     recordset.Close;
13. close a database
     db.Close;
14. free a DAO instance
     access := UnAssigned;
For example, you can call this code in some cycle. For some task is very
 useful.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:14:42 星期天 说道:

------------------------------------------------------------------------
--------
If you develop a database related software, then very popular task is to
 save some files (documents/images/reports/etc) in some BLOB field of 
table and use these saved data later.
In this tip I want to show how this task could be solved.
To save a file to BLOB:
blob := yourDataset.CreateBlobStream(yourDataset.
FieldByName('YOUR_BLOB'), bmWrite);
try
  blob.Seek(0, soFromBeginning);
  fs := TFileStream.Create('c:\your_name.doc', fmOpenRead 
orfmShareDenyWrite);
  try
    blob.CopyFrom(fs, fs.Size)
  finally
    fs.Free
  end;
finally
  blob.Free
end;
To load from BLOB:
blob := yourDataset.CreateBlobStream(yourDataset.
FieldByName('YOUR_BLOB'), bmRead);
try
  blob.Seek(0, soFromBeginning);
  with TFileStream.Create('c:\your_name.doc', fmCreate) do
    try
      CopyFrom(blob, blob.Size)
    finally
      Free
    end;
finally
  blob.Free
end;
Using this code you can work with any database engine 
(BDE/ADO/DAO/ODBC/DBISAM/etc) and any file format (document of MS Word,
 spreadsheet of MS Excel, bitmap or jpeg pictures, wav-files etc)

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:14:57 星期天 说道:

Reports, printing
#0017 Printer settings - page sizes and margins (part1) 
#0019 To get a printer settings (part 2) 
#0020 To set a printer settings 
#0022 Printing on the TPrinter using default resident font (part 1) 
#0026 The printer capabilities to print a graphics 
#0032 To change the default printer in Windows 
#0047 The list of jobs in the MS Windows print spooler 
#0068 To use a resident font for printing (part 2) 
#0083 To print/preview the MS Access's report 
#0087 To send a command strings to printer 
#0090 To print a bitmap on printer 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:15:39 星期天 说道:

------------------------------------------------------------------------
--------
From today I shall post a some useful functions for printer settings 
detect. Today I include a functions for printer page width/height and 
margins getting. 
function GetPageWidth: Integer;
begin
  Result := GetDeviceCaps(Printer.Handle, PHYSICALWIDTH)
end;
function GetPageHeight: Integer;
begin
  Result := GetDeviceCaps(Printer.Handle, PHYSICALHEIGHT)
end;
function GetPageOffsetLeft: Integer;
begin
  Result := GetDeviceCaps(Printer.Handle, PHYSICALOFFSETX)
end;
function GetPageOffsetRight: Integer;
begin
  Result := GetPageWidth - GetPageOffsetLeft - GetDeviceCaps(Printer.
Handle, HORZRES)
end;
function GetPageOffsetTop: Integer;
begin
  Result := GetDeviceCaps(Printer.Handle, PHYSICALOFFSETY)
end;
function GetPageOffsetBottom: Integer;
begin
  Result := GetPageHeight - GetPageOffsetTop - GetDeviceCaps(Printer.
Handle, VERTRES)
end;
function GetPixelsPerInchX: Integer;
begin
  Result := GetDeviceCaps(Printer.Handle, LOGPIXELSX)
end;
function GetPixelsPerInchY: Integer;
begin
  Result := GetDeviceCaps(Printer.Handle, LOGPIXELSY)
end;
PS: of course, the GetPage* and GetPageOffset* results will be in 
printer units. If you want to convert in screen units you must 
calculate: Result := Screen.PixelsPerInch*GetPage.. div 
GetPixelsPerInchX (or Y) 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:15:50 星期天 说道:

------------------------------------------------------------------------
--------
Today I continue to post a tips for printer settings. At first, you must
 open the printer device: 
At first, you must open the printer device:
var
  FDevice: PChar;
  FDriver: PChar;
  FPort: PChar;
  DeviceMode: THandle;
  DevMode: PDeviceMode;
procedure OpenThePrinterDevice;
var
  Driver_Info2: PDriverInfo2;
  Retrieved: dword;
  hPrinter: THandle;
begin
  Printer().GetPrinter(FDevice, FDriver, FPort, DeviceMode);
  if DeviceMode = 0 then
    Printer().GetPrinter(FDevice, FDriver, FPort, DeviceMode);
  OpenPrinter(FDevice, hPrinter, nil);
  GetMem(Driver_Info2, 255);
  GetPrinterDriver(hPrinter, nil, 2, Driver_info_2, 255, Retrieved);
  StrLCopy(FDriver, PChar(ExtractFileName(StrPas(Driver_Info2^.
PDriverPath)) + #0), 63);
  FreeMem(Driver_info_2, 255);
  DevMode := GlobalLock(DeviceMode);
end;
And now you can get the information from printer. For example, the 
orientation settings:
    if ((DevMode^.dmFields and DM_ORIENTATION) = DM_ORIENTATION) and
       (DevMode^.dmOrientation = DMORIENT_LANDSCAPE) then
      //Landscape
    else
      //Portrait
Also you can detect a paper size, paper source, print duplex and quality
 etc. View a const description in Windows.pas.
At last, don't forget to unlock a device:
  GlobalUnlock(DeviceMode);

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:16:01 星期天 说道:

------------------------------------------------------------------------
--------
The next part of the tip for printer settings.
At first, of course, you must open the printer device (as I described in
 previous tip " to get a printer settings").
Now you can set the any settings (supported only, of course) in 
properties of DevMode^ variable and add a "assigned" flag in DevMode^.
dmFields. After that you need call a SetPrinter procedure and unlock 
device.
View small example:
procedure SetPrinterSettings(FPrinter: TPrinter);
var
  FDevice: PChar;
  FDriver: PChar;
  FPort: PChar;
  DeviceMode: THandle;
  DevMode: PDeviceMode;
begin
  {to get a current printer settings}
  FPrinter.GetPrinter(FDevice, FDriver, FPort, DeviceMode);
  {lock a printer device}
  DevMode := GlobalLock(DeviceMode);
  {set a paper size as A4-Transverse}
  if ((DevMode^.dmFields and DM_PAPERSIZE) = DM_PAPERSIZE) then
  begin
    DevMode^.dmFields := DevMode^.dmFields or DM_PAPERSIZE;
    DevMode^.dmPaperSize := DMPAPER_A4_TRANSVERSE;
  end;
  {set a paper source as Tractor bin}
  if  ((DevMode^.dmFields and DM_DEFAULTSOURCE) = DM_DEFAULTSOURCE) 
then
  begin
    DevMode^.dmFields := DevMode^.dmFields or DM_DEFAULTSOURCE;
    DevMode^.dmDefaultSource := DMBIN_TRACTOR;
  end;
  {set a Landscape orientation}
  if  ((DevMode^.dmFields and DM_ORIENTATION) = DM_ORIENTATION) then
  begin
    DevMode^.dmFields := DevMode^.dmFields or DM_ORIENTATION;
    DevMode^.dmOrientation := DMORIENT_LANDSCAPE;
  end;
  {set a printer settings}
  FPrinter.SetPrinter(FDevice, FDriver, FPort, DeviceMode);
  {unlock a device}
  GlobalUnlock(DeviceMode);
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:16:10 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes for quick printing on the printer canvas I load a default 
resident font and draw on the canvas using it.
View a small example:
uses Printers;
procedure TForm1.Button1Click(Sender: TObject);
begin
    Printer.BeginDoc;
    Printer.Canvas.Font.Handle := GetStockObject(DEVICE_DEFAULT_FONT);
    Printer.Canvas.TextOut(100,  50, 'Text string1');
    Printer.Canvas.TextOut(100, 150, 'Text string2');
    Printer.EndDoc;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:16:20 星期天 说道:

------------------------------------------------------------------------
--------
If you want to detect if your printer is capable of printing graphics, 
large bitmaps, and DIBs, then you can check it by the next procedure:
begin
if (GetDeviceCaps(Printer.Canvas.Handle, RASTERCAPS) and RC_NONE) =
RC_NONE then
<graphic is not capable>;
if (GetDeviceCaps(Printer.Canvas.Handle, RASTERCAPS) and 
RC_STRETCHDIB) =
RC_STRETCHDIB then
<printer supports the StretchDIB>
else
<Windows will simulate the StretchDIB>;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:16:35 星期天 说道:

------------------------------------------------------------------------
--------
If you want to change the the default printer in Windows OS, you must 
change
the device key in the Windows section of the WIN.INI. After that you 
must
send a WM_WININICHANGE message:
var
  strIni: array[0..MAX_PATH] of Char;
  arrWindows: array[0..64] of Char;
begin
  GetWindowsDirectory(strIni, SizeOf(strIni));
  StrCat(strIni, '\win.ini');
  with TIniFile.Create(strIni) do
    try
      WriteString('windows', 'device', 'HP LaserJet 4 Plus,HPPCL5MS,
LPT1:');
    finally
      Free;
    end;
  StrCopy(arrWindows, 'windows');
  SendMessage(HWND_BROADCAST, WM_WININICHANGE, 0, 
LongInt(@arrWindows));
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:16:48 星期天 说道:

------------------------------------------------------------------------
--------
If you need detect how many jobs are in the MS Windows print spooler, 
then you must handle a WM_SPOOLERSTATUS message. When job is added or 
deleted in the spooler querue, this message will be broadcast.
So you must create a message handler for this message trapping:
type
  TyourForm = class(TForm)
  private
    { Private declarations }
    procedure WMSpoolerStatus(var Msg: TWMSpoolerStatus);
      message WM_SPOOLERSTATUS;
  public
    { Public declarations }
  end;
implementation
{$R *.DFM}
procedure TyourForm.WMSpoolerStatus(var Msg: TWMSpoolerStatus);
begin
  ShowMessage('Now in the spooler a ' + IntToStr(msg.JobsLeft) + ' 
jobs';
  msg.Result := 0;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:16:58 星期天 说道:

------------------------------------------------------------------------
--------
If you want to print a data on printer with max speed, you must print 
with resident font which installed in your printer.
At first, you must receive the handle of font (using the Windows API's 
GetStockObject function with DEVICE_DEFAUL_FONT parameter).
After that you must assign this handle to font handle of printer 
canvas.
procedure TForm1.Button1Click(Sender: TObject);
var
  tm: TTextMetric;
  i: Integer;
begin
  if PrintDialog1.Execute then
  begin
    Printer.BeginDoc;
    Printer.Canvas.Font.Handle := GetStockObject(DEVICE_DEFAULT_FONT);
    GetTextMetrics(Printer.Canvas.Handle, tm);
    for i := 1 to 10 do
      Printer.Canvas.TextOut(100, i*tm.tmHeight + tm.tmExternalLeading,
 'Test string');
    Printer.EndDoc;
  end;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:17:35 星期天 说道:

#83: How I can open a report (in Print Preview mode and also print 
direct) in an MS Access database?
------------------------------------------------------------------------
In the next small example I'll demonstrate how you can call the report 
in MS Access:
var 
  Access: Variant; 
begin 
  {open the Access application}
  try 
    Access := GetActiveOleObject('Access.Application'); 
  except 
    Access := CreateOleObject('Access.Application'); 
  end; 
  Access.Visible := True; 
  { open the database 
   The second parameter specifies whether you want to open the 
   database in Exclusive mode}
  Access.OpenCurrentDatabase('C:\My Documents\Books.mdb', True); 
  { open the report 
   The value for the second parameter should be one of 
   acViewDesign, acViewNormal, or acViewPreview. acViewNormal, which 
is the
   default, prints the report immediately. If you are not using the 
type
   library, you can define these values like this: 
  const 
    acViewNormal = $00000000; 
    acViewDesign = $00000001; 
    acViewPreview = $00000002; 
  The third parameter is for the name of a query in the current 
  database. The fourth parameter is for a SQL WHERE clause - the 
string must
  be valid SQL, minus the WHERE.} 
  Access.DoCmd.OpenReport('Titles by Author', acViewPreview, EmptyParam,
 EmptyParam); 
<...> 
  {close the database}
  Access.CloseCurrentDatabase; 
  {close the Access application}
  {const 
    acQuitPrompt = $00000000; 
    acQuitSaveAll = $00000001; 
    acQuitSaveNone = $00000002;} 
  Access.Quit(acQuitSaveAll); 
end; 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:17:49 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes useful to control a printer using direct command strings. It's
 a "DOS"-style but for some tasks this method is very useful.
For example, the Epson or HP printers have a lot of internal commands 
which allows to change a mode of printer, define the some parameters 
etc.
The everybody who tried to convert the DOS-programs from Pascal to 
Windows/Delphi, knows that the next statement
 WriteLn(lst, Chr(27) + '&l12D')
don't work in Windows. You must use the Escape function to send data 
directly to the printer.
View the example code which allows to send a command to printer:
type
  TPassThroughData = record
      nLen: Word;
      Data: array[0..255] of Byte;
  end;
procedure PrintText(s: string);
var
  PTBlock: TPassThroughData;
begin
  PTBlock.nLen := Length(s);
  StrPCopy(@PTBlock.Data, s);
  Escape(Printer.Handle, PASSTHROUGH, 0, @PTBlock, nil);
end;
procedure PrintOut;
begin
  Printer.BeginDoc;
  PrintText(#27'&l12D' + 'Hello, World!');
  Printer.EndDoc;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:18:09 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes you must from own application to print a some bitmap.
Of course, you can use the some report designer and create a report form
 with this bitmap. But you can solve this task and easy - to print a 
bitmap on printer canvas directly without any report engine.
Someone from you can say - easy task with solution in few lines of 
code:
with Printer do
begin
  BeginDoc;
  Canvas.Draw(X, Y, bmp);
  EndDoc;
end;
But this code will work correctly not with any printer! For example, 
Epson Stylus 300 this code will print the image with very bad quality 
and on some printer you can receive a blank paper... The reason of 
this - using the StretchBlt function for output but in MSDN you can read
 that this function does not guarantee a correct outputing from one 
device to other.
As alternative you must use the StretchDIBits function:
var
  Info: PBitmapInfo;
  InfoSize: DWORD;
  Image: Pointer;
  ImageSize: DWORD;
  Bits: HBITMAP;
  DIBWidth, DIBHeight: LongInt;
begin
  Printer.BeginDoc;
  try
    Canvas.Lock;
    try
      { Paint bitmap to the printer }
      with Printer do
      begin
        Bits := bmp.Handle;
        GetDIBSizes(Bits, InfoSize, ImageSize);
        Info := AllocMem(InfoSize);
        try
          Image := AllocMem(ImageSize);
          try
            GetDIB(Bits, 0, Info^, Image^);
            with Info^.bmiHeader do
            begin
              DIBWidth := biWidth;
              DIBHeight := biHeight;
            end;
            StretchDIBits(Canvas.Handle, 0, 0, DIBWidth, DIBHeight, 0, 
0,
              DIBWidth, DIBHeight, Image, Info^, DIB_RGB_COLORS, 
SRCCOPY);
          finally
            FreeMem(Image, ImageSize);
          end;
        finally
          FreeMem(Info, InfoSize);
        end;
      end;
    finally
      Canvas.Unlock;
    end;
  finally
    Printer.EndDoc;
  end;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:18:35 星期天 说道:

E-mail, Internet, Network
#0037 How to get your local IP? 
#0041 PC is connected to a network 
#0049 To create the e-mail message in MS Outlook 
#0080 To download the file from web 
#0088 How do I find a MAC address? 
#0095 To receive a modem list, which is installed in Win95/98 
#0115 To create an email messaqe by MAPI 
#0116 To download a file from internet using sockets 
#0117 To select a recipient from addressbook 
#0120 To retrieve a folder list from MS Outlook 
#0121 To retrieve items (messages/tasks/etc) from any Outllok folder 
#0123 To retrieve and save the attachments from MS Outllok message 
#0126 HTTP/Url encoding 
#0127 to print url/html file using IE browser 
#0139 TWebBrowser and POST request 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:18:52 星期天 说道:

------------------------------------------------------------------------
--------
Using the next code you can read a local IP address
function TForm1.LocalIP: string;
type
   TaPInAddr = array [0..10] of PInAddr;
   PaPInAddr = ^TaPInAddr;
var
    phe: PHostEnt;
    pptr: PaPInAddr;
    Buffer: array [0..63] of char;
    i: Integer;
    GInitData: TWSADATA;
begin
    WSAStartup($101, GInitData);
    Result := '';
    GetHostName(Buffer, SizeOf(Buffer));
    phe :=GetHostByName(buffer);
    if phe = nil then Exit;
    pptr := PaPInAddr(Phe^.h_addr_list);
    i := 0;
    while pptr^[i] <> nil do
    begin
      result:=StrPas(inet_ntoa(pptr^[i]^));
      Inc(i);
    end;
    WSACleanup;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:19:39 星期天 说道:

------------------------------------------------------------------------
--------
If you want to know if the PC is connected to a network under MS Windows
 then you can call a GetSystemMetrics() function (Windows API) with 
SM_NETWORK parameter:
if (GetSystemMetrics(SM_NETWORK) AND $01 = $01) then
    <PC is attached to network>
else
    <PC is not attached to network>

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:19:52 星期天 说道:

------------------------------------------------------------------------
--------
If you need create the message in your mailing app from your Delphi 
code, you can:
1. create a message via default mailer using shell api:
uses ShellAPI;
var
  pCh: PChar;
begin
  pCh := 'mailto:mshkolnik@scalabium.
com?subject=your_subject&body=your_body';
  ShellExecute(0, 'open', pCh, nil, nil, SW_SHOWNORMAL);
end;
Few additional comments:
1. the some mailers supports the extended syntax with some usefule 
features. For example, the MS Outlook supports the file attachment via 
ShellExecute:
var
  pCh: PChar;
begin
  pCh := 'mailto:mshkolnik@scalabium.
com?subject=your_subject&body=your_body&file="c:\autoexec.bat"';
  ShellExecute(0, 'open', pCh, nil, nil, SW_SHOWNORMAL);
end;
But other mailers don't supports this file attachment.
2. you must convert a texts which you want to place into subject or body
 - to change a spaces into "%20"
3. on some builds of MS Windows the all characters from subject and body
 will be trancated to small length or converted to lower case
2. create a message in Outlook using OLE:
const
  olMailItem = 0;
var
  Outlook, MailItem: OLEVariant;
begin
  try
    Outlook := GetActiveOleObject('Outlook.Application');
  except
    Outlook := CreateOleObject('Outlook.Application');
  end;
  MailItem := Outlook.CreateItem(olMailItem);
  MailItem.Recipients.Add('mshkolnik@scalabium.com');
  MailItem.Subject := 'your subject';
  MailItem.Body := 'Welcome to my homepage: http://www.scalabium.com';
  MailItem.Attachments.Add('C:\Windows\Win.ini');
  MailItem.Send;
  Outlook := Unassigned;
end;
If you want to create a message in html-format, you can use the HTMLBody
 property instead a Body. But note that this HTMLBody property is 
available staring from Outlook 98 only.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:20:05 星期天 说道:

------------------------------------------------------------------------
--------
If you needs to download a some file from web, you can use the next code
 of DownloadFile procedure.
For example,
begin
  Memo1.Lines.Text := DownloadFile('http://www.scalabium.com/sme/index.
htm')
end;
uses WinInet;
function DownloadFile(const Url: string): string;
var
  NetHandle: HINTERNET;
  UrlHandle: HINTERNET;
  Buffer: array[0..1024] of Char;
  BytesRead: dWord;
begin
  Result := '';
  NetHandle := InternetOpen('Delphi 5.x', INTERNET_OPEN_TYPE_PRECONFIG,
 nil, nil, 0);
  if Assigned(NetHandle) then 
  begin
    UrlHandle := InternetOpenUrl(NetHandle, PChar(Url), nil, 0, 
INTERNET_FLAG_RELOAD, 0);
    if Assigned(UrlHandle) then
      { UrlHandle valid? Proceed with download }
    begin
      FillChar(Buffer, SizeOf(Buffer), 0);
      repeat
        Result := Result + Buffer;
        FillChar(Buffer, SizeOf(Buffer), 0);
        InternetReadFile(UrlHandle, @Buffer, SizeOf(Buffer), 
BytesRead);
      until BytesRead = 0;
      InternetCloseHandle(UrlHandle);
    end
    else
      { UrlHandle is not valid. Raise an exception. }
      raise Exception.CreateFmt('Cannot open URL %s', [Url]);
    InternetCloseHandle(NetHandle);
  end
  else
    { NetHandle is not valid. Raise an exception }
    raise Exception.Create('Unable to initialize Wininet');
end;
PS: in this code I used the functions from WinInet.dll. This dll is a 
part of MS Internet Explorer so if you haven't the installed browser 
from Microsoft, this code will no workable (you'll receive the exception
 message).

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:20:15 星期天 说道:

------------------------------------------------------------------------
--------
The code below gets your MAC address. Should you have more than one 
NIC in your machine you will get the MAC off the first one.
function CoCreateGuid(var guid: TGUID): HResult; stdcall; far external 
'ole32.dll';
function Get_MACAddress: string;
var
  g: TGUID;
  i: Byte;
begin
  Result := '';
  CoCreateGUID(g);
  for i := 2 to 7 do
    Result := Result + IntToHex(g.D4[i], 2);
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:20:26 星期天 说道:

------------------------------------------------------------------------
--------
If you needs to retrieve a list of modems which was installed in MS 
Windows, you can use the next procedure:
function EnumModems: TStrings;
var
  R: TRegistry;
  s: ShortString;
  N: TStringList;
  i, j: integer;
begin
  Result:= TStringList.Create;
  R:= TRegistry.Create;
  try
    with R do
    begin
      RootKey:= HKEY_LOCAL_MACHINE;
      if OpenKey('\System\CurrentControlSet\Services\Class\Modem', 
False) then
        if HasSubKeys then
        begin
          N:= TStringList.Create;
          try
            GetKeyNames(N);
            for i := 0 to N.Count-1 do
            begin
              CloseKey;
              OpenKey(N[i], False);
              s:= ReadString('AttachedTo');
              for j := 1 to 4 do
                if Pos(Chr(j+Ord('0')), s) > 0 then
                  Break;
              Result.AddObject(ReadString('DriverDesc'), TObject(j));
              CloseKey;
            end;
          finally
            N.Free;
          end;
        end;
    end;
  finally
    R.Free;
  end;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:20:43 星期天 说道:

------------------------------------------------------------------------
--------
Few months ago I posted a code sample for email sending using MS Outlook
 as OLE-object. Today I want to post a tip for email sending using MAPI.
 Using MAPI you can control the default mailer whcih was installed in MS
 Windows - MS Outlook, MS Outlook Express, Lotus, Netscape etc
The SendMail procedure (view below) can be used in any your project - 
just call with parameteres:
function SendMail(const Subject, Body, FileName,
                  SenderName, SenderEMail,
                  RecipientName, RecipientEMail: string): Integer;
var
  Message: TMapiMessage;
  lpSender, lpRecipient: TMapiRecipDesc;
  FileAttach: TMapiFileDesc;
  SM: TFNMapiSendMail;
  MAPIModule: HModule;
begin
  FillChar(Message, SizeOf(Message), 0);
  with Message do
  begin
    if (Subject <> '') then
      lpszSubject := PChar(Subject);
    if (Body <> '') then
      lpszNoteText := PChar(Body);
    if (SenderEmail <> '') then
    begin
      lpSender.ulRecipClass := MAPI_ORIG;
      if (SenderName = '') then
        lpSender.lpszName := PChar(SenderEMail)
      else
        lpSender.lpszName := PChar(SenderName);
      lpSender.lpszAddress := PChar(SenderEmail);
      lpSender.ulReserved := 0;
      lpSender.ulEIDSize := 0;
      lpSender.lpEntryID := nil;
      lpOriginator := @lpSender;
    end;
    if (RecipientEmail <> '') then
    begin
      lpRecipient.ulRecipClass := MAPI_TO;
      if (RecipientName = '') then
        lpRecipient.lpszName := PChar(RecipientEMail)
      else
        lpRecipient.lpszName := PChar(RecipientName);
      lpRecipient.lpszAddress := PChar(RecipientEmail);
      lpRecipient.ulReserved := 0;
      lpRecipient.ulEIDSize := 0;
      lpRecipient.lpEntryID := nil;
      nRecipCount := 1;
      lpRecips := @lpRecipient;
    end
    else
      lpRecips := nil;
    if (FileName = '') then
    begin
      nFileCount := 0;
      lpFiles := nil;
    end
    else
    begin
      FillChar(FileAttach, SizeOf(FileAttach), 0);
      FileAttach.nPosition := Cardinal($FFFFFFFF);
      FileAttach.lpszPathName := PChar(FileName);
      nFileCount := 1;
      lpFiles := @FileAttach;
    end;
  end;
  MAPIModule := LoadLibrary(PChar(MAPIDLL));
  if MAPIModule = 0 then
    Result := -1
  else
    try
      @SM := GetProcAddress(MAPIModule, 'MAPISendMail');
      if @SM <> nil then
      begin
        Result := SM(0, Application.Handle, Message, MAPI_DIALOG or 
MAPI_LOGON_UI, 0);
      end
      else
        Result := 1;
    finally
      FreeLibrary(MAPIModule);
    end;
  if Result <> 0 then
    MessageDlg('Error sending mail (' + IntToStr(Result) + ').', 
mtError,
[mbOK], 0);
end;
PS: you must add the MAPI unit in USES-clause. To execute this 
procedure: 
procedure TForm1.Button1Click(Sender: TObject);
begin
  SendMail('Re: mailing from Delphi',
           'Welcome to http://www.scalabium.com'#13#10'Mike Shkolnik',
           'c:\autoexec.bat',
           'your name', 'your@address.com',
           'Mike Shkolnik', 'mshkolnik@scalabium.com');
end;
Of course, the any parameter of this procedure can be empty.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:20:55 星期天 说道:

------------------------------------------------------------------------
--------
Sometime ago I posted a code sample for file downloading using WinInet.
dll API of MS Internet Explorer). Today I want to post a tip for file 
downloading using sockets. Using sockets you can download any file 
from any site and no matter which browser type was used in MS Windows 
- Explorer, Netscape, Opera etc
The DownloadFile procedure (view below) can be used in any your 
project - just call with defined parameters:
procedure DownloadFile(strHost, strRemoteFileName, strLocalFileName: 
string;
 ClientSocket: TClientSocket);
var
  intReturnCode: Integer;
  s: string;
  szBuffer: array[0..128] of Char;
  FileOut: TFileStream;
begin
  if strRemoteFileName[1] <> '/' then
    strRemoteFileName := '/' + strRemoteFileName;
  FileOut := TFileStream.Create(strLocalFileName, fmCreate);
  try
    with ClientSocket do
    begin
      Host := strHost;
      ClientType := ctBlocking;
      Port := 80;
      try
        Open;
        {send query}
        s := 'GET ' + strRemoteFileName + '   HTTP/1.0'#13#10 +
             'Host: ' + strHost + #13#10#13#10;
        intReturnCode := Socket.SendBuf(Pointer(s)^, Length(s));
        if intReturnCode > 0 then
        begin
          {receive the answer}
          { iterate until no more data }
          while (intReturnCode > 0) do
          begin
            { clear buffer before each iteration }
            FillChar(szBuffer, SizeOf(szBuffer), 0);
            { try to receive some data }
            intReturnCode := Socket.ReceiveBuf(szBuffer, 
SizeOf(szBuffer));
            { if received a some data, then add this data to the 
result string }
            if intReturnCode > 0 then
              FileOut.Write(szBuffer, intReturnCode);
          end
        end
        else
          MessageDlg('No answer from server', mtError, [mbOk], 0);
        Close;
      except
        MessageDlg('No connection', mtError, [mbOk], 0);
      end;
    end;
  finally
    FileOut.Free
  end;
end;
To execute this procedure: 
procedure TForm1.Button1Click(Sender: TObject);
begin
  DownloadFile('www.scalabium.com', '/forums.htm', 'd:\forums.htm', 
ClientSocket1);
end;
PS: the last parameter is ClientSocket component which you must drop 
on form from component pallete or create in run-time.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:21:07 星期天 说道:

------------------------------------------------------------------------
--------
Today I want to post a tip about copying of recipients from default 
addressbook. For example, user must select a list of recipients and 
you'll process this list in own code (to send a message to these 
recipients or just import them into own database).
For this task you can use the MAPIAddress procedure from MAPI. This 
procedure requires a handle of current session (which you'll receive 
from MAPILogOn procedure), custom caption of dialog, structure for 
recepient attributes and variable where you'll receive a number of 
selected recipients.
If MSPIAddress returns SUCCESS_SUCCESS, this mean that user closed a 
dialog and selected some recipients. After that you must navigater by 
recipient structure (which you defined as parameter) and process the 
each recipient.
For example:
var
  lpRecip: TMapiRecipDesc;
  intRecips: ULONG;
  lpRecips: PMapiRecipDesc;
  i: Integer;
begin
  if (MAPIAddress(intMAPISession, 0, 'Select the recipients', 4, '', 0,
 lpRecip, 0, 0, @intRecips, lpRecips) = SUCCESS_SUCCESS) then
  begin
    for i := 0 to intRecips-1 do
      yourListBox.Items.Add(PMapiRecipDesc(PChar(lpRecips) + 
i*SizeOf(TMapiRecipDesc))^.lpszAddress);
    MAPIFreeBuffer(lpRecips)
  end;
end;
where intMAPISession is an integer varaiable wchich store the handle 
of current MAPI session. If you don't use the MapiLogOn which return 
this handle, just place a zero value there:
intMAPISession := 0;
I hope that this tip will help you and save some time. At least when I 
wrote (in October 2000) this code for own GroupMail, I spent a lot of 
time for correct work without errors:-)

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:21:19 星期天 说道:

------------------------------------------------------------------------
--------
I want to post a few tips which are replated to MS Outlook application 
and interaction with it from Delphi. The procedure below allow to load a
 tree of available folders into TTreeView:
procedure TfrmMain.RetrieveOutlookFolders(tvFolders: TTreeView);
  procedure LoadFolder(ParentNode: TTreeNode; Folder: OleVariant);
  var
    i: Integer;
    node: TTreeNode;
  begin
    for i := 1 to Folder.Count do
    begin
      node := tvFolders.Items.AddChild(ParentNode, Folder.Item[i].Name;
      LoadFolder(node, Folder.Item[i].Folders);
    end;
  end;
var
  outlook, NameSpace: OLEVariant;
begin
  outlook := CreateOleObject('Outlook.Application');
  NameSpace := outlook.GetNameSpace('MAPI');
  LoadFolder(nil, NameSpace.Folders);
  outlook := UnAssigned;
end;
A few comments:
1. the data in Outlook have the next structure: outlook application 
defines a MAPI's namespace which have a collection of folders. Each 
folder contains an items or sub-folders
2. this code load a full tree in TreeView. Of course, if you have a 
lot of pst-files with messages (active, archive, backup etc) and each of
 this pst-file have a large structure of folders, this code will work 
slowly. So as suggestion: you can rewrite a code and load the one 
level only. In this case code will work quickly and a list of 
sub-folders you'll receive in OnExpanding event of your TreeView
3. each folder of Outlook have an unique idenifier. You can save it 
somewhere (for example, in Data property of TTreeNode). Remember that 
this ID is long string value which you can receive as EntryID in loop of
 LoadFolder procedure: Folder.Item[i].EntryID
PS: if this topic is interested for you, I'll continue this serie of 
tips and shall show how to load the messages/contacts/tasks/etc from 
some folder or create a new item.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:21:41 星期天 说道:

------------------------------------------------------------------------
--------
Today I want to continue a story about MS Outlook programming.
In previous tip I described how you can retrieve a folder structure 
and the next task is to read the items from some folder. As you remember
 each folder have the unique identifier (EntryID). So when you want to 
read items, you must find this folder by id in MAPI namespace:
  outlook := CreateOleObject('Outlook.Application');
  NameSpace := outlook.GetNameSpace('MAPI');
  Folder := NameSpace.GetFolderByID(EntryID);
After that you must retrieve a type of items in this folder:
intFolderType := Folder.DefaultItemType;
where intFolderType: Integer and can have the one from the next values:
olMailItem $00000000 
olAppointmentItem $00000001 
olContactItem $00000002 
olTaskItem $00000003 
olJournalItem $00000004 
olNoteItem $00000005 
olPostItem $00000006 
For example, your folder have an olMailItem type. So the items in your 
folder are email messages and the next task is to retrieve a list of 
these messages.
Each folder have an Items collection where all items are placed.
To read a number of messages in folder you must check an Folder.Items.
Count.
So the task to navigate by messages is very sample - easy looping.
Important note:
in VBA the first index in collection is 1 instead 0 as in Delphi:
for i := 1 to Folder.Items.Count do
begin
  oiItem := Folder.Items[i];
...
end;
Now when you have each item in this folder, you can read any property of
 this item. Note that available property list depends from folder type.
 For
example, for olMailItem you can read the SenderName, Subject, 
ReceivedName, ReceivedByName etc but for olAppointmentItem these 
properties are not
available because this folder type have the ReplyTime, Subject and other
 properties
Of course, within your looping you can write a standard case-of 
statement,
check a folder type and read the specific properties for each folder 
type.
For example:
case intFolderType of
  olMailItem: s := VarToStr(oiItem.SenderName) + oiItem.Subject + 
oiItem.ReceivedTime + oiItem.ReceivedByName;
  olAppointmentItem: s := oiItem.Subject + oiItem.ReplyTime;
  olContactItem: s := oiItem.FullName + oiItem.Email;
  olTaskItem: s := oiItem.SenderName + oiItem.DueDate + oiItem.
PercentComplete;
  olJournalItem: s := oiItem.SenderName;
  olNoteItem: s := oiItem.Subject + oiItem.CreationTime + oiItem.
LastModificationTime;
  olPostItem: s := VarToStr(oiItem.SenderName) + oiItem.Subject + 
oiItem.ReceivedTime;
end;
Of course, you can place each item property into separated variable - 
I just shown the string concatenation as example. It's very nice to 
place the items into string grid or listview but it depends from your 
client application

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:22:02 星期天 说道:

------------------------------------------------------------------------
--------
I want to continue the postings about MS Outlook programming in 
Borland Delphi.
Today I want to show how you can recieve a list of files in attachment 
for any message and save some from these attachments to disk:
Each message have the Attachments collection which contains a list of 
files. As usual collection, the Attachments property have the Count 
property where you can read a number of files and access to each file 
using Item method.
For example, in code below you'll see how to add in TListView 
component the rows for each attachment file:
for i := 1 to itemOL.Attachments.Count do
  with lvAttachments.Items.Add do
    Caption := itemOL.Attachments.Item(i);
Of course, don't forget that VB's collection have the 1 as starting 
index.
Also each item in Attachments collection have the SaveAsFile method 
which allow to save an attachment into file on disk. The parameter of 
SaveAsFile is a desired file name.
itemOL.Attachments.Item(i).SaveAsFile(itemOL.Attachments.Item(i));
I prepeared a small demo application where I included all described tips
 for MS Outlook programming. You can download this project with sources:
 http://www.scalabium.com/faq/delphioutlook.zip (150Kb)
PS: if you'll have some questions or suggestions, please contact me

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:22:32 星期天 说道:

------------------------------------------------------------------------
--------
Everybody, who wrote some tools for web, know that 
urls/filename/script parameters etc are encoded (contains the %xx 
substrings).
For example,
http://groups.google.com/groups?q=http%3A%2F%2Fwww.scalabium.
com&hl=ru&meta= site%3Dgroups or http://groups.google.
com/groups?th=1223ec006df22d25&seekm=3b98e41d_2%40dnews
Of course, in code you must decode a value in "real" value. The 
algorithm is easiest - after % character the next 2 positions are 
decimal code of character.
But also for this task you can use the HTTPEncode function from 
HttpApp unit:
function HTTPEncode(const AStr: String): String;
const
  NoConversion = ['A'..'Z','a'..'z','*','@','.','_','-'];
var
  Sp, Rp: PChar;
begin
  SetLength(Result, Length(AStr) * 3);
  Sp := PChar(AStr);
  Rp := PChar(Result);
  while Sp^ <> #0 do
  begin
    if Sp^ in NoConversion then
      Rp^ := Sp^
    else
      if Sp^ = ' ' then
        Rp^ := '+'
      else
      begin
        FormatBuf(Rp^, 3, '%%%.2x', 6, [Ord(Sp^)]);
        Inc(Rp,2);
      end;
    Inc(Rp);
    Inc(Sp);
  end;
  SetLength(Result, Rp - PChar(Result));
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:22:44 星期天 说道:

------------------------------------------------------------------------
--------
today I want to show how you can activate printing of any url and/or 
html file using installed IE.
I solved this task yesterday and solution is very useful and have a 
small size:-)
uses ComObj;
procedure PrintHTMLByIE(const url: string);
const
  OLECMDID_PRINT = $00000006;
  OLECMDEXECOPT_DONTPROMPTUSER = $00000002;
var
  ie, vaIn, vaOut: Variant;
begin
  ie := CreateOleObject('InternetExplorer.Application');
  ie.Navigate(url);
  ie.Visible := True;
  ie.ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_DONTPROMPTUSER, vaIn, vaOut);
end;
Sample:
PrintHTMLByIE('file:\\c:\misha\webpage\index.htm');
or
PrintHTMLByIE('http:\\www.scalabium.com\sme\index.htm');

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:22:55 星期天 说道:

------------------------------------------------------------------------
--------
Today I want to describe how you can send some data to web-server from 
your remote script using TWebBrowser component.
var
  strData: string;
  PostData: OleVariant;
  Headers: OleVariant;
  i: Integer;
begin
  {1. you must create a string with parameter names and values
  Result string must be in the next format:
  Param1=Value1&Param2=Value2&Param3=Value3...}
  strData := 'Param1Name=' + HTTPEncode(Param1Value) + '&' +
    'Param2Name=' + HttpEncode(Param2Value) + ...;
  {2. you must convert a string into variant array of bytes and
   every character from string is a value in array}
  PostData := VarArrayCreate([0, Length(strData) - 1], varByte);
  { copy the ordinal value of the character into the PostData array}
  for i := 1 to Length(strData) do
    PostData[i-1] := Ord(strData[i]);
  {3. prepare headers which will be sent to remote web-server}
  Headers := 'Content-Type: application/x-www-form-urlencoded' + 
#10#13;
  {4. you must navigate to the URL with your script and send as 
parameters
  your array with POST-data and headers}
  yourWebBrowserComponent.Navigate('http://www.yourdomain.
com/your_post_script.asp', EmptyParam, EmptyParam, PostData, Headers);
end;
Of course, the same task can be solved with any internet component which
 implement the http-client (not only TWebBrowser). But for every 
component the data for script will be posted by own rules (most 
components have pre-defined properties for this task).
For example, you can use my TSMIHTTPClient component from SMInternet 
suite. There you must set Action=haPost and fill the Headers property 
with own Post-data.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:23:20 星期天 说道:

Multimedia
#0015 To open/close a CD-drive 
#0078 To sound a beep for some action 
#0091 To play a wav-file from memory or BLOB-field 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:23:36 星期天 说道:

------------------------------------------------------------------------
--------
1. If you want to open a CD-drive from your program, you need:
mciSendString('Set cdaudio door open wait', nil, 0, Handle);
2. If you want to close a CD-drive from your program, you need:
mciSendString('Set cdaudio door closed wait', nil, 0, Handle);

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:23:47 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes in development very useful to sound a "context" beep. For 
example, user entered the incorrect value - you can show the message 
dialog with information about it but also useful to beep with "error" 
sound.
Try this - it's very useful for end-user as context help/assistent:
uses Windows;
procedure PlayBeep(ActionType: TMsgDlgType);
var mb: dWord;
begin
  case ActionType of
    mtInformation: mb := MB_ICONASTERISK; //SystemAsterisk
    mtWarning: mb := MB_ICONEXCLAMATION; //SystemExclamation
    mtError: mb := MB_ICONHAND; //SystemHand
    mtConfirmation: mb := MB_ICONQUESTION; //SystemQuestion
    mtCustom: mb := MB_OK; //SystemDefault
  else
    mb:= $0FFFFFFFF; //Standard beep using the computer speaker
  end;
  MessageBeep(mb);
end;
PS: don't use it very frequently because users'll be angry:)

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:23:58 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes you needs to play a some sounds from own app but store these 
sounds in BLOB-field of table or in some external files.
To play a wav-file you must call the PlaySound procedure which have 
the few parameters - sound (which you want to play), handle of file with
 resource (only for wav from exe/dll-file) and flags for playing.
For example, if you want to play a wav from BLOB, you can use the next 
code:
var
  b: TBlobStream;
  m: TMemoryStream;
begin
  b := TBlobStream.Create(yourTable, bmRead);
  try
    m := TMemoryStream.Create;
    try
      {copy a wav from BLOB into memory}
      m.CopyFrom(b, b.Size);
      {Play it but if we'll have an error, raise exception}
      Win32Check(PlaySound(m.Memory, 0, SND_SYNC or SND_MEMORY));
    finally
      m.Free
    end;
  finally
    b.Free
  end;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:24:15 星期天 说道:

Conversions
#0042 To convert the bitmap to Jpeg and vice versa 
#0044 To convert a color string into HTML format 
#0045 To convert color value into gray-scaled color value 
#0132 To add leading zeros for numbers 
#0137 To create a disabled bitmap 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:24:35 星期天 说道:

------------------------------------------------------------------------
--------
You can use the next code for conversion of bitmap into jpeg or jpeg 
into bitmap:
uses Jpeg;
procedure Bmp2Jpeg(const BmpFileName, JpgFileName: string);
var
  Bmp: TBitmap;
  Jpg: TJPEGImage;
begin
  Bmp := TBitmap.Create;
  Jpg := TJPEGImage.Create;
  try
    Bmp.LoadFromFile(BmpFileName);
    Jpg.Assign(Bmp);
    Jpg.SaveToFile(JpgFileName);
  finally
    Jpg.Free;
    Bmp.Free;
  end;
end;
procedure Jpeg2Bmp(const BmpFileName, JpgFileName: string);
var
  Bmp: TBitmap;
  Jpg: TJPEGImage;
begin
  Bmp := TBitmap.Create;
  Jpg := TJPEGImage.Create;
  try
    Jpg.LoadFromFile(JpgFileName);
    Bmp.Assign(Jpg);
    Bmp.SaveToFile(BmpFileName);
  finally
    Jpg.Free;
    Bmp.Free;
  end;
end;
I uses these procedures for picture format changing in own PictView 
application.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:24:46 星期天 说道:

------------------------------------------------------------------------
--------
If you want to create a HTML-file, you must define a tag for font 
color or backgroubd color. But you can't insert a Delphi's TColor 
value - you must convert the color into RGB-format. In own SMExport 
suite I use the next
function:
function GetHTMLColor(cl: TColor; IsBackColor: Boolean): string;
var rgbColor: TColorRef;
begin
  if IsBackColor then
    Result := 'bg'
  else
    Result := '';
  rgbColor := ColorToRGB(cl);
  Result := Result + 'color="#' + Format('%.2x%.2x%.2x',
                                            [GetRValue(rgbColor),
                                             GetGValue(rgbColor),
                                             GetBValue(rgbColor)]) + 
'"';
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:24:55 星期天 说道:

------------------------------------------------------------------------
--------
If you want to convert a colored image into same gray scaled, then you 
must convert the color of the each pixel by the next schema:
function RgbToGray(Source: TColor) : TColor;
var Target: Byte;
begin
  Target := Round((0.30 * GetRValue(Source)) +
        (0.59 * GetGValue(Source)) +
        (0.11 * GetBValue(Source)));
  Result := RGB(Target, Target, Target);
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:25:11 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes you need add the leading zeros to numbers for formatted 
printing of report forms or data exporting.
For this task I wrote a small function which return a formatted string 
(see below the AddLeadingZeros function).
But today I found that standard Format function allow to do a same!
Format('%.10d', [15]) will return '0000000015'
Don't ask me why in Delphi help you'll not find the description of 
this formatted string or why I didn't find it before:-(
Below is my old function:
function AddLeadingZeros(const Source: string; Len: Integer): string;
var
  i: Integer;
begin
  Result := Source;
  for i := 1 to (Len-Length(Source)) do
    Result := '0' + Result;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:25:36 星期天 说道:

------------------------------------------------------------------------
--------
After some delay when I released a lot of new versions/components I want
 to post a new tip:-)
Everyone from you saw that standard TSpeedButton allow to show a 
loaded glyph in "disabled" state when your original glyph will be 
converted into gray-scheme.
Sometimes to create similar bitmap is useful not only for TSpeedButton.
You can use the next my CreateDisabledBitmap procedure where such 
"disabled" bitmap (Destination parameter) will be created from your 
original bitmap (Source).
procedure CreateDisabledBitmap(Source, Destination: TBitmap);
const
  ROP_DSPDxax = $00E20746;
var
  DDB, MonoBmp: TBitmap;
  IWidth, IHeight: Integer;
  IRect: TRect;
begin
  IWidth := Source.Width;
  IHeight := Source.Height;
  Destination.Width := IWidth;
  Destination.Height := IHeight;
  IRect := Rect(0, 0, IWidth, IHeight);
  Destination.Canvas.Brush.Color := clBtnFace;
  Destination.Palette := CopyPalette(Source.Palette);
  MonoBmp := nil;
  DDB := nil;
  try
    MonoBmp := TBitmap.Create;
    DDB := TBitmap.Create;
    DDB.Assign(Source);
    DDB.HandleType := bmDDB;
    { Create a disabled version }
    with MonoBmp do
    begin
      Assign(Source);
      HandleType := bmDDB;
      Canvas.Brush.Color := clBlack;
      Width := IWidth;
      if Monochrome then
      begin
        Canvas.Font.Color := clWhite;
        Monochrome := False;
        Canvas.Brush.Color := clWhite;
      end;
      Monochrome := True;
    end;
    with Destination.Canvas do
    begin
      Brush.Color := clBtnFace;
      FillRect(IRect);
      Brush.Color := clBtnHighlight;
      SetTextColor(Handle, clBlack);
      SetBkColor(Handle, clWhite);
      BitBlt(Handle, 1, 1, IWidth, IHeight,
             MonoBmp.Canvas.Handle, 0, 0, ROP_DSPDxax);
      Brush.Color := clBtnShadow;
      SetTextColor(Handle, clBlack);
      SetBkColor(Handle, clWhite);
      BitBlt(Handle, 0, 0, IWidth, IHeight,
             MonoBmp.Canvas.Handle, 0, 0, ROP_DSPDxax);
    end;
  finally
    DDB.Free;
    MonoBmp.Free;
  end;
  Source.Dormant;
end;
Sample of use:
procedure TfrmMain.ButtonClick(Sender: TObject);
var
  Destination: TBitmap;
begin
  Destination := TBitmap.Create;
  try
    CreateDisabledBitmap(Image1.Picture.Bitmap, Destination);
    Image2.Picture.Bitmap.Assign(Destination);
  finally
    Destination.Free
  end
end;
where Image1 is TImage where you have an original bitmap and TImage2 
will a container for created disabled bitmap.
Hope this tip will be useful for someone from you...

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:26:37 星期天 说道:

Algorithms
#0039 To save/load a font information in INI/text-file 
#0048 CRC32 calculation 
#0056 To play with colors (dark/light) 
#0093 Hash function for strings 
#0113 Correct round of number 
#0124 extended DayOfWeek function 
#0125 second Wednesday of November 
#0129 checksum by modulus 10 
#0136 ELF hash algorithm 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:26:40 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes you need to save/to load a font information in INI-file, 
Registry or some text file.
Now I desribe the some different methods.
1. very easy but not result isn't compact and effective (by data 
storage)
procedure SaveFont(FStream: TIniFile; Section: string; smFont: TFont);
begin
  FStream.WriteString(Section, Ident + 'Name', smFont.Name);
  FStream.WriteInteger(Section, Ident + 'CharSet', smFont.CharSet);
  FStream.WriteInteger(Section, Ident + 'Color', smFont.Color);
  FStream.WriteInteger(Section, Ident + 'Size', smFont.Size);
  FStream.WriteInteger(Section, Ident + 'Style', Byte(smFont.Style));
end;
procedure LoadFont(FStream: TIniFile; Section: string; smFont: TFont);
begin
  smFont.Name := FStream.ReadString(Section, Ident + 'Name', smFont.
Name);
  smFont.CharSet := TFontCharSet(FStream.ReadInteger(Section, Ident +  
'CharSet', smFont.CharSet));
  smFont.Color := TColor(FStream.ReadInteger(Section, Ident + 'Color', 
smFont.Color));
  smFont.Size := FStream.ReadInteger(Section, Ident + 'Size', smFont.
Size);
  smFont.Style := TFontStyles(Byte(FStream.ReadInteger(Section, Ident 
+ 'Style', Byte(smFont.Style))));
end;
2. more hardly than first method, but result is compact. I use this 
method in all own apps.
procedure SaveFont(FStream: TIniFile; Section: string; smFont: TFont);
begin
  FStream.WriteString(Section, 'Font', smFont.Name + ',' +
                                       IntToStr(smFont.CharSet) + ',' 
+
                                       IntToStr(smFont.Color) + ',' +
                                       IntToStr(smFont.Size) + ',' +
                                       IntToStr(Byte(smFont.Style)));
end;
procedure LoadFont(FStream: TIniFile; Section: string; smFont: TFont);
var s, Data: string;
    i: Integer;
begin
  s := FStream.ReadString(Section, 'Font', ',,,,');
  try
    i := Pos(',', s);
    if i > 0 then
    begin
      {Name}
      Data := Trim(Copy(s, 1, i-1));
      if Data <> '' then
        smFont.Name := Data;
      Delete(s, 1, i);
      i := Pos(',', s);
      if i > 0 then
      begin
        {CharSet}
        Data := Trim(Copy(s, 1, i-1));
        if Data <> '' then
          smFont.Charset := TFontCharSet(StrToIntDef(Data, smFont.
Charset));
        Delete(s, 1, i);
        i := Pos(',', s);
        if i > 0 then
        begin
          {Color}
          Data := Trim(Copy(s, 1, i-1));
          if Data <> '' then
            smFont.Color := TColor(StrToIntDef(Data, smFont.Color));
          Delete(s, 1, i);
          i := Pos(',', s);
          if i > 0 then
          begin
           {Size}
           Data := Trim(Copy(s, 1, i-1));
           if Data <> '' then
             smFont.Size := StrToIntDef(Data, smFont.Size);
           Delete(s, 1, i);
           {Style}
           Data := Trim(s);
           if Data <> '' then
             smFont.Style := TFontStyles(Byte(StrToIntDef(Data, 
Byte(smFont.Style))));
          end
        end
      end
    end;
  except
  end;
end;
3. as alternative for 1&2 methods I can offer the third - you can create
 a temporary stream, save the wished font component in this stream 
(Stream.SaveComponent) and then you can navigate the byte-by-byte in 
stream, to convert each byte into hex (or some other radix) and save 
into your text file as string. Each byte is a two symbols for hex radix.
 For font reading - on the contrary...
PS: personally I not used this method:)))

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:26:53 星期天 说道:

------------------------------------------------------------------------
--------
If you need calculate a CRC32 for some file or some string, then you 
must do:
1. build a CRC table
2. calculate a CRC for each line of your file
3. calculate a total CRC
1. CRC table creation:
type
  Long = record
    LoWord: Word;
    HiWord: Word;
  end;
const
  CRCPOLY = $EDB88320;
var
  CRCTable: array[0..512] Of Longint;
procedure BuildCRCTable;
var
  i, j: Word;
  r: Longint;
begin
  FillChar(CRCTable, SizeOf(CRCTable), 0);
  for i := 0 to 255 do
  begin
    r := i shl 1;
    for j := 8 downto 0 do
      if (r and 1) <> 0 then
        r := (r Shr 1) xor CRCPOLY
      else
        r := r shr 1;
    CRCTable[i] := r;
   end;
end;
2. CRC calculation for file:
function RecountCRC(b: byte; CrcOld: Longint): Longint;
begin
  RecountCRC := CRCTable[byte(CrcOld xor Longint(b))] xor ((CrcOld shr 
8) and $00FFFFFF)
end;
function HextW(w: Word): string;
const
  h: array[0..15] Of char = '0123456789ABCDEF';
begin
  HextW := '';
  HextW := h[Hi(w) shr 4] + h[Hi(w) and $F] + h[Lo(w) shr 4]+h[Lo(w) and
 $F];
end;
function HextL(l: Longint): string;
begin
  with Long(l) do
    HextL := HextW(HiWord) + HextW(LoWord);
end;
function GetCRC32(FileName: string): string;
var
  Buffer: PChar;
  f: File of Byte;
  b: array[0..255] of Byte;
  CRC: Longint;
  e, i: Integer;
begin
  BuildCRCTable;
  CRC := $FFFFFFFF;
  AssignFile(F, FileName);
  FileMode := 0;
  Reset(F);
  GetMem(Buffer, SizeOf(B));
  repeat
    FillChar(b, SizeOf(b), 0);
    BlockRead(F, b, SizeOf(b), e);
    for i := 0 to (e-1) do
     CRC := RecountCRC(b[i], CRC);
  until (e < 255) or (IOresult <> 0);
  FreeMem(Buffer, SizeOf(B));
  CloseFile(F);
  CRC := Not CRC;
  Result := '$' + HextL(CRC);
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:27:03 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes in run-time you need play with colors. For example, you have 
the some color value and you want to make it more dark or light. The 
next two function were written for it.
function Dark(Col: TColor; Percent: Byte): TColor;
var R, G, B: Byte;
begin
  R := GetRValue(Col);
  G := GetGValue(Col);
  B := GetBValue(Col);
  R := Round(R*Percent/100);
  G := Round(G*Percent/100);
  B := Round(B*Percent/100);
  Result := RGB(R, G, B);
end;
function Light(Col: TColor; Percent: Byte): TColor;
var R, G, B: Byte;
begin
  R := GetRValue(Col);
  G := GetGValue(Col);
  B := GetBValue(Col);
  R := Round(R*Percent/100) + Round(255 - Percent/100*255);
  G := Round(G*Percent/100) + Round(255 - Percent/100*255);
  B := Round(B*Percent/100) + Round(255 - Percent/100*255);
  Result := RGB(R, G, B);
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:27:14 星期天 说道:

------------------------------------------------------------------------
--------
Today I found a good hash function in delphi sources (dbtables.pas):
function GetHashCode(Str: PChar): Integer;
var
  Off, Len, Skip, I: Integer;
begin
  Result := 0;
  Off := 1;
  Len := StrLen(Str);
  if Len < 16 then
    for I := (Len - 1) downto 0 do
    begin
      Result := (Result * 37) + Ord(Str[Off]);
      Inc(Off);
    end
  else
  begin
    { Only sample some characters }
    Skip := Len div 8;
    I := Len - 1;
    while I >= 0 do
    begin
      Result := (Result * 39) + Ord(Str[Off]);
      Dec(I, Skip);
      Inc(Off, Skip);
    end;
  end;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:27:25 星期天 说道:

------------------------------------------------------------------------
--------
From first times when was released a Borland Pascal, in sources was 
included a "bank" algorithm of number rounding.
For example,
Round(25.5) = 26
but
Round(26.5) = 26.
For banks or some other financial organizations this algorithm is good 
but in other type of calculations we want a standard rounding. For 
this tasks I use the own small function that I use instead standard:
function CorrectRound(x: Extended): LongInt;
begin
  Result := Trunc(x);
  if (Frac(x) >= 0.5) then
    Result := Result + 1;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:27:38 星期天 说道:

------------------------------------------------------------------------
--------
As you know, the default DayOfWeek function from SysUtils unit returns 
the day of week for some date. But in this function uses the Sunday as 
first day of the week and the Saturday as the seventh.
In some countries this function is not useful because the first day of 
the week is a Monday instead Sunday. The function below can be used in 
this case:
For example, in code below you'll see how to add in TListView 
component the rows for each attachment file:
function DayOfWeekMon2Sun(dtDate: TDateTime): Integer;
const
  StartOfWeek: Integer = 2;
begin
  Result := (DayOfWeek(dtDate) - StartOfWeek + 7) mod 7 + 1;
end;
I wrote this function a lot of time ago when I started the development 
of scheduler program and wanted to add a possibility to define a 
custom timesheet (like in MS Outlook).
I hope that this function will be useful for you too.
PS: as you see, I used the const value (StartOfWeek) as shift. So if 
your country use the some other order in days of the week, you can 
change this const value.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:27:49 星期天 说道:

------------------------------------------------------------------------
--------
Today I want to continue a posting of small routunies for date playing.
In any scheduler you can define the desired time of execution as "Second
 Wednesday" of some month/year. To calculate such dates is very simple 
task.
I'll show it on small sample. For example, you want to calculate a 
date of second Wednesday of November in the 2001.
In this case you must:
1. to receive a date of first day in November 2001:
dt := EncodeDate(2001, 11, 1);
2. to calculate a first Wednesday in the November:
while (DayOfWeek(dt) <> 4) do
dt := dt + 1;
3. to calculate a next Wednesday:
dt := dt + 7;
The similar method you can use for calculation of last Wednesday of 
November 2001:
1. to receive a date of last day in November 2001:
dt := EncodeDate(2001, 11, 30);
2. to calculate a last Wednesday in the November:
while (DayOfWeek(dt) <> 4) do
dt := dt - 1; 
Of course, you can use the similar code for calculation of third 
Sunday or fourth Friday in any month of any year.
I used this schema in own application for database synchronization which
 supports a custom scheduler and my users like this feature. I hope that
 you'll add something like that in the own applications.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:28:07 星期天 说道:

------------------------------------------------------------------------
--------
Today I want to publish a code for checksum calculation by modulus 10.
This algirithm is very popular for UPC barcodes (Universal Product 
Code), hash code or serial number generation for applications etc...
The basic algorithm:
add the values of the digits in the odd positions (1, 3, 5...) 
multiply this result by 3 
add the values of the digits in the even positions (2, 4, 6...) 
sum the results of steps 2 and 3 
the check digit is the smallest number which, when added to the result 
in step 4, produces a multiple of 10. 
Small example. Assume the source data is 08137919805
0+1+7+8+5=22 
22*3=66 
8+3+9+9+0=29 
66+29=95 
95+??=100 where ?? is a 5 (our checksum) 
My implementation in the Pascal:
function Mod10(const Value: string): Integer;
var
  i, intOdd, intEven: Integer;
begin
  {add all odd seq numbers}
  intOdd := 0;
  i := 1;
  while (i < Length(Value)) do
  begin
    Inc(intOdd, StrToIntDef(Value[i], 0));
    Inc(i, 2);
  end;
  {add all even seq numbers}
  intEven := 0;
  i := 2;
  while (i < Length(Value)) do
  begin
    Inc(intEven, StrToIntDef(Value[i], 0));
    Inc(i, 2);
  end;
  Result := 3*intOdd + intEven;
  {modulus by 10 to get}
  Result := Result mod 10;
  if Result <> 0 then
    Result := 10 - Result
end;
You can expand this algorithm for own needs.
For example, I modified it and\par now I use it for any characters 
(not only digits) in source value.
The original algorithm I use for UPC-barcode validation in the 
SMReport Designer and the my extended algorithm I use in the serial 
number generation as part of the protection schema (in the shareware 
projects).

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:28:33 星期天 说道:

------------------------------------------------------------------------
--------
Sometime ago I posted a code for hash calculation of strings (CRC32 
and easy method which is extracted from VCL sources). Today I want to 
post a code for ELF algorithm which allow to receive a strong 
distributed hash from string. This algorithm is very popular because can
 be implemented in any programming language (from VB to Java) and is a 
very-very fast (contain one easy loop only).
See a code below:
function ElfHash(const Value: string): Integer;
var
  i, x: Integer;
begin
  Result := 0;
  for i := 1 to Length(Value) do
  begin
    Result := (Result shl 4) + Ord(Value[i]);
    x := Result and $F0000000;
    if (x <> 0) then
      Result := Result xor (x shr 24);
    Result := Result and (not x);
  end;
end;
A few examples you can see in table below:
Text value
 Calculated elf-hash
Scalabium Software 189378421 
Scalabium 136833325 
scalabium.com 153641565 
www.scalabium.com 156665277 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:30:01 星期天 说道:

Files, drives
#0031 To detect a drive type 
#0046 To get a file datetime 
#0050 How do I execute a program and have my code wait until it is 
finished? 
#0051 To create a shell link/shortcut 
#0055 To get the serial number of a disk 
#0058 To accept the dropped files from Windows Explorer 
#0066 The file in use or not 
#0075 To map the network disk 
#0114 To load a file list by some wildcard 
#0118 To recieve a number of files in some folder by wildcard 
#0122 To change a creation date/time for file on disk 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:30:39 星期天 说道:

------------------------------------------------------------------------
--------
If you want to detect a drive type (CD/Floppy/Network...), then you must
 use a GetDriveType function from Windows API.
For example,
  case GetDriveType('C:\') of
    0: <unknown>;
    1: <root directory does not exist>;
    DRIVE_REMOVABLE: <floppy>;
    DRIVE_FIXED: <hard drive>;
    DRIVE_REMOTE: <network>;
    DRIVE_CDROM: <CD-ROM>;
    DRIVE_RAMDISK: <RAM disk>;
  end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:30:48 星期天 说道:

------------------------------------------------------------------------
--------
If you want to receive a datetime of the specified file, then you must 
to call a FileAge function and convert the returned value using the 
FileDateToDateTime function into a TDateTime value:
function GetFileDateTime(FileName: string): TDateTime;
var intFileAge: LongInt;
begin
  intFileAge := FileAge(FileName);
  if intFileAge = -1 then
    Result := 0
  else
    Result := FileDateToDateTime(intFileAge)
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:31:01 星期天 说道:

------------------------------------------------------------------------
--------
The next procedure allows you to execute a program and to wait until 
it's finished: 
function WinExecAndWait32(FileName: string; Visibility: Integer): 
dWord;
var
  zAppName: array[0..512] of Char;
  zCurDir: array[0..255] of Char;
  WorkDir: string;
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
begin
  StrPCopy(zAppName, FileName);
  GetDir(0, WorkDir);
  StrPCopy(zCurDir, WorkDir);
  FillChar(StartupInfo, Sizeof(StartupInfo), #0);
  StartupInfo.cb := Sizeof(StartupInfo);
  StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
  StartupInfo.wShowWindow := Visibility;
  if not CreateProcess(nil,
           zAppName, { pointer to command line string }
           nil, { pointer to process security attributes }
           nil, { pointer to thread security attributes }
           false, { handle inheritance flag }
           CREATE_NEW_CONSOLE or { creation flags }
           NORMAL_PRIORITY_CLASS,
           nil, { pointer to new environment block }
           nil, { pointer to current directory name }
           StartupInfo, { pointer to STARTUPINFO }
           ProcessInfo) then
    Result := -1 { pointer to PROCESS_INF }
  else
  begin
    WaitforSingleObject(ProcessInfo.hProcess, INFINITE);
    GetExitCodeProcess(ProcessInfo.hProcess, Result);
    CloseHandle(ProcessInfo.hProcess);
    CloseHandle(ProcessInfo.hThread);
  end;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:31:17 星期天 说道:

------------------------------------------------------------------------
--------
If you need create a shell link from your application (for example, from
 you setup app), you can use the next code:
procedure CreateShortCut(ShortCut, Application, Parameters, WorkDir:
string; SW_State: Integer; IconFile: string; IconIndex: Byte);
var
  SCObject: IUnknown;
  SCSLink: IShellLink;
  SCPFile: IPersistFile;
  WFName: WideString;
begin
  SCObject := CreateComObject(CLSID_ShellLink);
  SCSLink := SCObject as IShellLink;
  SCPFile := SCObject as IPersistFile;
  SCSLink.SetPath(PChar(Application));
  SCSLink.SetArguments(PChar(Parameters));
  SCSLink.SetWorkingDirectory(PChar(WorkDir));
  SCSLink.SetShowCmd(SW_State);
  SCSLink.SetIconLocation(PChar(IconFile), IconIndex);
  WFName := ShortCut;
  SCPFile.Save(PWChar(WFName), False);
end;
For example:
CreateShortCut(ShortCut, Application, Parameters, WorkDir: string; 
SW_State: Integer; IconFile: string; IconIndex: Byte);

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:31:59 星期天 说道:

------------------------------------------------------------------------
--------
If you need retrieve the serial number of a disk, you can call the 
GetVolumeInformation function from Windows API. View example:
var
  VolumeSerialNumber: DWORD;
  MaximumComponentLength: DWORD;
  FileSystemFlags: DWORD;
  SerialNumber: string;
begin
  GetVolumeInformation('C:\',
                       nil,
                       0,
                       @VolumeSerialNumber,
                       MaximumComponentLength,
                       FileSystemFlags,
                       nil,
                       0);
  SerialNumber := IntToHex(HiWord(VolumeSerialNumber), 4) +
                  '-' +
                  IntToHex(LoWord(VolumeSerialNumber), 4);
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:33:08 星期天 说道:

------------------------------------------------------------------------
--------
If you need accept the dropped files from the some external 
application (for example, from Windows Explorer), you must call the 
DragAcceptFiles for your form (in which you want to accept) and handle 
the WM_DROPFILES message. 
I used this algoritm when I wrote the small editor.
I post the next code as simple example:
type 
  TForm1 = class(TForm)
    ListBox1: TListBox;
    procedure FormCreate(Sender: TObject);
  protected
    procedure WMDROPFILES (var Msg: TWMDropFiles); message 
WM_DROPFILES;
  private
  public
  end;
var
  Form1: TForm1;
implementation
uses ShellApi;
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
  DragAcceptFiles(Handle, True);
end;
procedure TForm1.WMDROPFILES(var Msg: TWMDropFiles);
var
  i, amount: Integer;
  FileName: array[0..MAX_PATH] of Char;
begin
  inherited;
  try
    Amount := DragQueryFile(Msg.Drop, $FFFFFFFF, FileName, MAX_PATH);
    for i := 0 to (Amount - 1) do
    begin
      DragQueryFile(Msg.Drop, i, FileName, MAX_PATH);
      listbox1.items.add(FileName);
    end;
  finally
    DragFinish(Msg.Drop);
  end;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:33:19 星期天 说道:

------------------------------------------------------------------------
--------
In some situations of your applications you must check: the file is 
locked for exclusive access or not. The next function you can use for 
this check:
function FileInUse(FileName: string): Boolean;
var hFileRes: HFILE;
begin
  Result := False;
  if not FileExists(FileName) then exit;
  hFileRes := CreateFile(PChar(FileName),
                                    GENERIC_READ or GENERIC_WRITE,
                                    0,
                                    nil,
                                    OPEN_EXISTING,
                                    FILE_ATTRIBUTE_NORMAL,
                                    0);
  Result := (hFileRes = INVALID_HANDLE_VALUE);
  if not Result then 
    CloseHandle(hFileRes);
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:33:37 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes in run-time you must map/unmap the some remote resource. You 
can make it with the next code:
uses Windows;
var nw: TNetResource;
begin
  nw.dwType := RESOURCETYPE_DISK;
  nw.lpLocalName := nil;
  nw.lpRemoteName := PChar('\\yourServerName\yourDiskOnServer');
  nw.lpProvider := nil;
  errCode := WNetAddConnection2(nw, nil, nil, 0);
  if errCode = NO_ERROR then
  <Ok, disk mapped>
end;
If the remote server have the password, then you must call the
errCode := WNetAddConnection2(nw, 'strPassword', nil, 0);
If you want to map this disk as own local resource, you must define 
the lpLocalName. For example,
nw.lpLocalName := 'F:';
When you want to un-map resource, you must call the 
WNetCancelConnection2 procedure.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:33:52 星期天 说道:

------------------------------------------------------------------------
--------
After small delay with tip posting I want to continue this important 
task:-)
Today I'll post a small sample which show how you can receive a list 
of files from some specific directory by some custom wildcard. For 
example, to load in listbox the bmp-files from Windows directory.
procedure LoadFilesByMask(lst: TStrings; const SpecDir, WildCard: 
string);
var
  intFound: Integer;
  SearchRec: TSearchRec;
begin
  lst.Clear;
  intFound := FindFirst(SpecDir + WildCard, faAnyFile, SearchRec);
  while intFound = 0 do
  begin
    lst.Add(SpecDir + SearchRec.Name);
    intFound := FindNext(SearchRec);
  end;
  FindClose(SearchRec);
end;
For example, to load bmp-files from application folder:
LoadFilesByMask(lbFiles.Items, ExtractFilePath(Application.ExeName), 
'*.bmp')

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:34:04 星期天 说道:

------------------------------------------------------------------------
--------
Today I want to post tip with small function which allow to calculate 
a number of files in a specified directory.
function GetFilesCount(Folder, WildCard: string): Integer;
var
  intFound: Integer;
  SearchRec: TSearchRec;
begin
  Result := 0;
  if (Folder <> '') and (Folder[Length(Folder)] <> '\') then
    Folder := Folder + '\';
  intFound := FindFirst(Folder + WildCard, faAnyFile, SearchRec);
  while (intFound = 0) do
  begin
    if not (SearchRec.Attr and faDirectory = faDirectory) then
      Inc(Result);
    intFound := FindNext(SearchRec);
  end;
  FindClose(SearchRec);
end;
To use:
i := GetFilesCount('d:\', '*.zip'); //retrieve a number of 
zip-archives
i := GetFilesCount('c:\windows\', '*.bmp'); //retrieve a number of 
bitmaps
i := GetFilesCount('c:\My Documents', '*.*'); //retrieve a total 
number of any files

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:34:17 星期天 说道:

------------------------------------------------------------------------
--------
Today I want to show how you can change a datetime of file creation from
 own application:
function SetDateToFile(const FileName: string; Value: TDateTime): 
Boolean;
var
  hFile: THandle;
  intResult: Integer;
begin
  Result := False;
  try
    {open a file handle}
    hFile := FileOpen(FileName, fmOpenWrite or fmShareDenyNone);
    {if opened succesfully}
    if (hFile > 0) then
      {convert a datetime into DOS format and set a date}
      intResult := (FileSetDate(hFile, DateTimeToFileDate(Value)) = 0)
  finally
    {close an opened file handle}
    FileClose(hFile);
  end;
end;
To use:
SetDateToFile('C:\My Documents\application.cfg', Now()); 
This code can be useful in case of application protection - you can 
store in creation date of some your file a same important value. For 
example, version of application or date of first start for your trial.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:34:39 星期天 说道:

Menu
#0030 To add item into system menu 
#0033 To add bitmaps to menu items 
#0060 To align a menu item to right 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:34:57 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes I must insert a new items into system menu of some form. I 
think that I such not one...
type TyourForm = class(TForm)
  private
    { Private declarations }
    procedure wmSysCommand(var Message:TMessage); message 
WM_SYSCOMMAND;
  end;
const
  ID_ABOUT  = Word(-1);
implementation
procedure TyourForm.wmSysCommand;
begin
  if Message.wParam = ID_ABOUT then
  begin
     <some actions>
  end;
  inherited;
end;
procedure TyourForm.FormCreate(Sender: TObject);
var sysMenu: THandle;
begin
  sysMenu := GetSystemMenu(Handle, False);
  AppendMenu(sysMenu, MF_SEPARATOR, Word(-1), '');
  AppendMenu(sysMenu, MF_BYPOSITION, ID_ABOUT, 'About...');
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:35:08 星期天 说道:

------------------------------------------------------------------------
--------
From Delphi 4 you can add the bitmaps to your menu items.
But in Delphi 2/3 you haven't such useful possibility.
I use the next method:
1. you must declare a TBitmap variable:
var bmp: TBitmap
2. load the bitmap (from file, resource etc):
bmp.LoadFromFile('c:\yourPath\...\copy.bmp');
In default MS Windows settings the glyphs in menu have a 12x12pixels. If
 your bitmap have other size, then this bitmap will stretched.
3. set bitmap to menu item:
SetMenuItemBitmaps(yourMenu.Handle, intIndex, MF_BYPOSITION, bmp.Handle,
 bmp.Handle),
where:
- yourMenu is a menu component (TMainMenu or TPopupMenu)
- intIndex is a menu item position (from 0)
- first bmp.Handle is a handle of bitmap for unchecked state
- second bmp.Handle is a handle of bitmap for checked state
Of course, you can set the same bmp.Handle or set the different 
bitmaps for unchecked/checked states
PS: some days ago the one registered user ask me: "why in your 
SMReport Designer you not use a glyphs in menu?". I forgot it! Of 
course, yesterday I included such feature in few minutes and "SMReport 
use the bitmaps in menu and is MS Office compatible now...":))

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:35:20 星期天 说道:

------------------------------------------------------------------------
--------
The Microsoft GUI guide not allow to change the align of some menu 
item but from DOS-time personally I like to align a Help menu item to 
right.
But in some applications from Microsoft I saw the right-aligned menu 
item too:) If you want to align the item in own application, you can 
call the ModifyMenu function with MF_POPUP or MF_HELP flag.
View example:
uses 
  Windows; 
procedure TForm1.FormCreate(Sender: TObject); 
begin 
  ModifyMenu(yourMainMenu.Handle, 0, MF_BYPOSITION or MF_POPUP or 
MF_HELP, yourMenuItem.Handle, 'yourCaption'); 
end; 
The altrenative method:
var
  MMI: TMenuItemInfo;
  MyMenu: hMenu;
  Buffer: array[0..79] of Char;
begin
  MyMenu := GetMenu(Handle);
  // Handle is the handle of the form that contains the menu
  MMI.cbSize := SizeOf(MMI);
  MMI.fMask := MIIM_TYPE;
  MMI.dwTypeData := Buffer;
  MMI.cch := SizeOf(Buffer);
  GetMenuItemInfo(MyMenu, 1, True, MMI);
  // (..., 1, True, ...)  means that help is the second menu item.
  MMI.fType := MMI.fType or MFT_RIGHTJUSTIFY;
  SetMenuItemInfo(MyMenu, 1, True, MMI);
end;
PS: I'm not sure that this code will work on each Windows platform.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:35:39 星期天 说道:

Tools API
#0003 How can I check the Delphi/C++Builder version? 
#0040 To read/write a property by name 
#0054 To get a name of enum value 
#0069 To receive a names of all registered components 
#0070 To receive a names of all palettes, which was registered in IDE 
#0073 To receive the list of published properties of component (part 
1) 
#0074 To receive the list of published properties of component (part 
2) 
#0103 To change a default font in IDE 
#0104 To disable a new project creation 
#0119 To register a component editor which will be actavted by right 
mouse click in Delphi IDE 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:36:14 星期天 说道:

------------------------------------------------------------------------
--------
*   VER70 is Borland Pascal 7.0
*   VER80 is Delphi 1.0
*   VER90 is Delphi 2.0
*   VER93 is BCB++ 1.0
*   VER100 is Delphi 3.0
*   VER110 is BCB++ 3.0
*   VER120 is Delphi 4.0
*   VER125 is BCB++4.0
*   VER130 is Delphi 5.0
*   VER140 is Delphi 6.0
*   VER150 is Delphi 7.0
In program source you can check this value. For example, in sources of 
TSMDBGrid component I use
the next:
var
  {$IFDEF VER120}
  I: TColorRef;
  {$ELSE}
  I: Integer;
  {$ENDIF}

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:36:31 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes you need to change a some property for each components. Of 
course, if these components have a common ancestor, then you can write:
var i: Integer;
begin
  for i := 0 to ComponentCount-1 do
    if Components[i] is TyourAncestor then
       TyourAncestor(Components[i]).yourProperty := yourValue;
end;
For example,
var i: Integer;
begin
  for i := 0 to ComponentCount-1 do
    if Components[i] is TSpeedButton then
       TSpeedButton(Components[i]).Flat := True;
end;
But frequently you need change a property but the components haven't a 
common ancestor. In an example above you can have a TSpeedButton's 
components and TDBNavigator component on same panel. These components 
have taken place from different ancesors and you must change a 
property as different. You can make following:
var i: Integer;
    PropInfo: PPropInfo;
begin
  for i := 0 to ComponentCount-1 do
  begin
    PropInfo := GetPropInfo(Components[i].ClassInfo, yourPropertyName);
    {if such property exists}
    if Assigned(PropInfo) then 
      SetOrdProp(Components[i], PropInfo, LongInt(yourPropertyValue));
  end;
end;
Of course, if you have not ordinary property (like Flat), you need 
call a different procedure. The following procedures were declared in 
typinfo.pas:
procedure SetOrdProp(Instance: TObject; PropInfo: PPropInfo;
  Value: Longint);
procedure SetStrProp(Instance: TObject; PropInfo: PPropInfo;
  const Value: string);
procedure SetFloatProp(Instance: TObject; PropInfo: PPropInfo;
  Value: Extended);
procedure SetVariantProp(Instance: TObject; PropInfo: PPropInfo;
  const Value: Variant);
procedure SetMethodProp(Instance: TObject; PropInfo: PPropInfo;
  const Value: TMethod);

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:36:41 星期天 说道:

------------------------------------------------------------------------
--------
For example, if you have the some enum type
TyourEnumType = (One, Two, Three, Four, Five, Six, Seven, Eight, Nine, 
Ten)
and you want in run-time to get a string with same value for each of 
them (for example, fill the combobox items with enum values), then you 
can use the next procedure:
uses TypInfo;
var i: Integer;
begin
  for i := Ord(Low(TyourEnumType)) to Ord(High(TyourEnumType)) do
    Combobox1.Items.Add(GetEnumName(TypeInfo(TyourEnumType), i));
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:36:51 星期天 说道:

------------------------------------------------------------------------
--------
If you want to write the own expert for Delphi/C++Builder IDE and you 
want to display the names of all registered components, you can use 
the next code:
uses TypInfo, ToolIntf, Exptintf;
procedure GetComponentNames(lst: TStrings);
var
  i, k: Integer;
  CRef: TClass;
  strName: ShortString;
begin
  lst.Clear;
  for i := 0 to ToolServices.GetModuleCount-1 do
  begin
    for k := 0 to ToolServices.GetComponentCount(i)-1 do
      begin
       CRef := TClass(GetClass(ToolServices.GetComponentName(i, k)));
       while CRef <> nil do
       begin
         strName := CRef.ClassName;
         if lst.IndexOf(strName) = -1 then
           lst.Add(strName);
         if str <> 'TComponent' then
          CRef := CRef.ClassParent
         else
           CRef := nil;
       end;
      end;
  end;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:37:00 星期天 说道:

------------------------------------------------------------------------
--------
To continue the IDE expert subject:))
If you want to write the own expert for Delphi/C++Builder IDE and you 
want to display the names of all registered palette, you can use the 
next code:
uses Registry;
procedure GetPaletteNames(lst: TStrings);
var i: Integer;
begin
  with TRegistry.Create do
    try
      if OpenKeyReadOnly('Software\Borland\Delphi\4.0\Palette') then
         GetValueNames(lst);
    finally
      Free;
    end;
  for i := 0 to lst.Count-1 do
    if Pos('.', lst.Strings[i]) > 0 then
      lst.Delete(i);
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:37:09 星期天 说道:

------------------------------------------------------------------------
--------
If you needs a list of published properties for some component, you must
 use the GetPropInfos function in TypInfo unit. For example,
uses TypInfo;
procedure GetPublishedProperties(comp: TComponent; lst: TStrings);
var
  Props: PPropList;
  TypeData: PTypeData;
  i: Integer;
begin
  TypeData := GetTypeData(comp.ClassInfo);
  if (TypeData = nil) or (TypeData^.PropCount = 0) then Exit;
  GetMem(Props, TypeData^.PropCount * sizeof(Pointer));
  try
    GetPropInfos(comp.ClassInfo, Props);
    for i := 0 to TypeData^.PropCount-1 do
    begin
      with Props^[i]^ do
        lst.Add(Name);
    end;
  finally
    FreeMem(Props);
  end;
end;
PS: I started to develop the component like Object Inspector for 
SMReport Designer. So maybe in some future I'll finish it... 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:37:21 星期天 说道:

------------------------------------------------------------------------
--------
Yesterday I posted the tip about list of published properties. Today I 
found the second method to do a same but this method is easy:
var
  i, j: Integer;
  PropList: TPropList;
begin
  GetPropList(Obj.ClassInfo, TypeKinds, @PropList);
  j := High(PropList);
  i := 0;
  while (i < j) and Assigned(PropList[i]) do
  begin
    lbInspector.Items[i] := PropList[i].Name + ': ' + PropList[i].
PropType^.Name;
    Inc(i)
  end
end;
where
TypeKinds := tkProperties
or
TypeKinds := tkMethods

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:37:29 星期天 说道:

------------------------------------------------------------------------
--------
Each time when you must add a new form in own project, this form will be
 created with MS SanSerif font as default font.
Of course, you can save a some form in the repository and create a 
newest forms from this saved form or inherits this form from some own 
"parent" form but you have the other solution too - you can define a 
default font for forms. It's easy!
You must open the DELPHI.INI file (in Windows folder) and add the 
FormDesign section. In this section you must add a value with default 
font.
For example:
[FormDesign]
DefaultFont=Arial, 9

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:37:42 星期天 说道:

------------------------------------------------------------------------
--------
In the last weeks I had a lot of work with releases of SMLogging and 
SMExport and I didn't post a delphi tips. In the next days I want to 
release the SMImport but today I want to post a two tips:))
Each time when you run a Delphi you receive the new project with empty 
form. You can change an IDE setting and set the option for loading of 
last project. It's useful but what you must do when you want to run 
the IDE without project opening? Solution is easy: you must run the 
deklphi with additional parameter:
delphi32.exe /np
Just change the command line in your shortcut and will be happy:))

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:37:56 星期天 说道:

------------------------------------------------------------------------
--------
Today I want to show how you can register a custom component editor in 
Delphi IDE.
For example, you wrote some own component and want to add some menu 
items which will be shown in design-time popupmenu. You can assign a lot
 of useful "shortcut" methods there.
For example, for any export/import component I assigned two menu items:
 About and Execute, for report components I defined three menu items: 
About, Preview and Print.
Such method is very useful for components. For example, for standard 
TPageControl you can can in one click to add a new page or navigate by 
pages (Next/Prev). 
For this feature you must write and register a component editor for your
 component:
type
  TyourNewComponentEditor = class(TComponentEditor)
    procedure ExecuteVerb(Index: Integer); override;
    function GetVerb(Index: Integer): string; override;
    function GetVerbCount: Integer; override;
  end;
where
function TyourNewComponentEditor.GetVerbCount: Integer;
begin
  {total number of your newest items in popup menu}
  Result := 2;
end;
function TyourNewComponentEditor.GetVerb(Index: Integer): string;
begin
  {names for each item}
  case Index of
    0: Result := 'Item1...';
    1: Result := 'Item2...';
  end;
end;
procedure TyourNewComponentEditor.ExecuteVerb(Index: Integer);
begin
  {actions for each item}
  with (Component as TyourNewComponent) do
    case Index of
      0: MethodForItem1;
      1: MethodForItem2;
    end
end;
and to register it:
procedure Register;
begin
  RegisterComponentEditor(TyourNewComponent, TyourNewComponentEditor);
end;
It's all:-)

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:38:13 星期天 说道:

OLE, COM
#0049 To create the e-mail message in Outlook 
#0051 To create a shell link/shortcut 
#0083 print/preview the MS Access's report 
#0086 To register the file extention and context menu 
#0089 Description of Instancing and Threading Models in COM-servers 
#0120 To retrieve a folder list from MS Outlook 
#0128 To create an appointment in MS Outlook 
#0142 To to read a sender address for MailItem (MS Outlook) 
#0143 To check if OLE object is installed 
#0144 Fast data transfer to MS Excel 
#0145 Contact list in MS Outlook 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:38:52 星期天 说道:

------------------------------------------------------------------------
--------
If you need create the message in your mailing app from your Delphi 
code, you can:
1. create a message via default mailer using shell api:
uses ShellAPI;
var
  pCh: PChar;
begin
  pCh := 'mailto:mshkolnik@scalabium.
com?subject=your_subject&body=your_body';
  ShellExecute(0, 'open', pCh, nil, nil, SW_SHOWNORMAL);
end;
Few additional comments:
1. the some mailers supports the extended syntax with some usefule 
features. For example, the MS Outlook supports the file attachment via 
ShellExecute:
var
  pCh: PChar;
begin
  pCh := 'mailto:mshkolnik@scalabium.
com?subject=your_subject&body=your_body&file="c:\autoexec.bat"';
  ShellExecute(0, 'open', pCh, nil, nil, SW_SHOWNORMAL);
end;
But other mailers don't supports this file attachment.
2. you must convert a texts which you want to place into subject or body
 - to change a spaces into "%20"
3. on some builds of MS Windows the all characters from subject and body
 will be trancated to small length or converted to lower case
2. create a message in Outlook using OLE:
const
  olMailItem = 0;
var
  Outlook, MailItem: OLEVariant;
begin
  try
    Outlook := GetActiveOleObject('Outlook.Application');
  except
    Outlook := CreateOleObject('Outlook.Application');
  end;
  MailItem := Outlook.CreateItem(olMailItem);
  MailItem.Recipients.Add('mshkolnik@scalabium.com');
  MailItem.Subject := 'your subject';
  MailItem.Body := 'Welcome to my homepage: http://www.scalabium.com';
  MailItem.Attachments.Add('C:\Windows\Win.ini');
  MailItem.Send;
  Outlook := Unassigned;
end;
If you want to create a message in html-format, you can use the HTMLBody
 property instead a Body. But note that this HTMLBody property is 
available staring from Outlook 98 only.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:39:07 星期天 说道:

------------------------------------------------------------------------
--------
If you need create a shell link from your application (for example, from
 you setup app), you can use the next code:
procedure CreateShortCut(ShortCut, Application, Parameters, WorkDir:
string; SW_State: Integer; IconFile: string; IconIndex: Byte);
var
  SCObject: IUnknown;
  SCSLink: IShellLink;
  SCPFile: IPersistFile;
  WFName: WideString;
begin
  SCObject := CreateComObject(CLSID_ShellLink);
  SCSLink := SCObject as IShellLink;
  SCPFile := SCObject as IPersistFile;
  SCSLink.SetPath(PChar(Application));
  SCSLink.SetArguments(PChar(Parameters));
  SCSLink.SetWorkingDirectory(PChar(WorkDir));
  SCSLink.SetShowCmd(SW_State);
  SCSLink.SetIconLocation(PChar(IconFile), IconIndex);
  WFName := ShortCut;
  SCPFile.Save(PWChar(WFName), False);
end;
For example:
CreateShortCut(ShortCut, Application, Parameters, WorkDir: string; 
SW_State: Integer; IconFile: string; IconIndex: Byte);
------------------------------------------------------------------------
--------

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:39:17 星期天 说道:

--------
#83: How I can open a report (in Print Preview mode and also print 
direct) in an MS Access database?
------------------------------------------------------------------------
--------
In the next small example I'll demonstrate how you can call the report 
in MS Access:
var 
  Access: Variant; 
begin 
  {open the Access application}
  try 
    Access := GetActiveOleObject('Access.Application'); 
  except 
    Access := CreateOleObject('Access.Application'); 
  end; 
  Access.Visible := True; 
  { open the database 
   The second parameter specifies whether you want to open the 
   database in Exclusive mode}
  Access.OpenCurrentDatabase('C:\My Documents\Books.mdb', True); 
  { open the report 
   The value for the second parameter should be one of 
   acViewDesign, acViewNormal, or acViewPreview. acViewNormal, which 
is the
   default, prints the report immediately. If you are not using the 
type
   library, you can define these values like this: 
  const 
    acViewNormal = $00000000; 
    acViewDesign = $00000001; 
    acViewPreview = $00000002; 
  The third parameter is for the name of a query in the current 
  database. The fourth parameter is for a SQL WHERE clause - the 
string must
  be valid SQL, minus the WHERE.} 
  Access.DoCmd.OpenReport('Titles by Author', acViewPreview, EmptyParam,
 EmptyParam); 
<...> 
  {close the database}
  Access.CloseCurrentDatabase; 
  {close the Access application}
  {const 
    acQuitPrompt = $00000000; 
    acQuitSaveAll = $00000001; 
    acQuitSaveNone = $00000002;} 
  Access.Quit(acQuitSaveAll); 
end; 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:40:46 星期天 说道:

------------------------------------------------------------------------
--------
When you create a new COM object, you must define the Instancing and 
Threading Model, which will be used in your COM object. For example, 
when you use the Automation Object Wizard (select File|New from main 
menu, click the ActiveX tab and select Automation Object Wizard item) 
you must select a desired values of Instancing/Threading Model from 
comboboxes.
In this article I'll include a short description for each value.
1. Instancing refers to how many instances of the COM object are created
 for a request of client. You can select a one option from three 
available items: Single Instance, Multiple Instance and Internal.
a) Single Instance - allows only a single COM interface for each 
executable. So creating multiple single instances results in creating 
multiple copies of the server application. This option is commonly 
used for multiple document interface (MDI) applications.
b) Multiple Instance - specifies that multiple applications can 
connect to the object. Any time a client requests service, a separete 
instance of the server gets invoked. That is, there can be multiple 
instances in a single executable. For example, any time a user 
attempts to open the Windows Explorer, a separate Explorer is created 
c) Internal - means the object can only be created internally. An 
external application cannot create an instance of the object directly. 
For example, a word processor application may have an internal 
document object that can only be created by calling a method of the 
application that will create the document object.
2. The Threading Model refers to how your object is advertised to the 
client applications via its thread support. You can select one from 
the next options: Single, Apartment, Free or Both.
a) Single - no thread support. Client requests are serialized by the 
standard calling mechanism. serialized by the calling mechanism. With 
this threading model the clients are handled one at a time so no 
threading
support is needed.
b) Apartment - different objects from the same server can be called on 
different threads or different clients, but each object is called only 
from that one thread. If two clients need to use the same object, they 
have to take turns. With this threading model the instance data is safe,
 global data must be protected using critical sections or some other 
form of serialization. Of course, the thread's local variables are 
reliable across multiple calls.
c) Free - clients can call any method of object from any thread at any 
time. Objects can handle any number of threads at any time. That is, 
more than one client can share the same object. Objects must protect all
 instance and global data using critical sections or some other form 
of serialization. Thread local variables are not reliable across 
multiple calls. Primarily used for distributed DCOM environments.
d) Both - objects can support clients that use either Aprtment or Free 
threading models. This threading model give a maximum performance and 
flexibility.
So a correct value selection of Instancing and Threading Model can 
affect on performance of your COM object and depends from task which you
 want realize in this object.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:41:00 星期天 说道:

------------------------------------------------------------------------
--------
I want to post a few tips which are replated to MS Outlook application 
and interaction with it from Delphi. The procedure below allow to load a
 tree of available folders into TTreeView:
procedure TfrmMain.RetrieveOutlookFolders(tvFolders: TTreeView);
  procedure LoadFolder(ParentNode: TTreeNode; Folder: OleVariant);
  var
    i: Integer;
    node: TTreeNode;
  begin
    for i := 1 to Folder.Count do
    begin
      node := tvFolders.Items.AddChild(ParentNode, Folder.Item[i].Name;
      LoadFolder(node, Folder.Item[i].Folders);
    end;
  end;
var
  outlook, NameSpace: OLEVariant;
begin
  outlook := CreateOleObject('Outlook.Application');
  NameSpace := outlook.GetNameSpace('MAPI');
  LoadFolder(nil, NameSpace.Folders);
  outlook := UnAssigned;
end;
A few comments:
1. the data in Outlook have the next structure: outlook application 
defines a MAPI's namespace which have a collection of folders. Each 
folder contains an items or sub-folders
2. this code load a full tree in TreeView. Of course, if you have a 
lot of pst-files with messages (active, archive, backup etc) and each of
 this pst-file have a large structure of folders, this code will work 
slowly. So as suggestion: you can rewrite a code and load the one 
level only. In this case code will work quickly and a list of 
sub-folders you'll receive in OnExpanding event of your TreeView
3. each folder of Outlook have an unique idenifier. You can save it 
somewhere (for example, in Data property of TTreeNode). Remember that 
this ID is long string value which you can receive as EntryID in loop of
 LoadFolder procedure: Folder.Item[i].EntryID
PS: if this topic is interested for you, I'll continue this serie of 
tips and shall show how to load the messages/contacts/tasks/etc from 
some folder or create a new item.
------------------------------------------------------------------------
--------

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:41:11 星期天 说道:

------------------------------------------------------------------------
--------
Today I want to continue a serie of tips for MS Outlook automatization 
from Delphi.
If you want to create a new appointment, you can use a code sample 
below:
uses ComObj;
procedure CreateNewAppointment;
const
  olAppointmentItem = $00000001;
  olImportanceLow = 0;
  olImportanceNormal = 1;
  olImportanceHigh = 2;
  {to find a default Contacts folder}
  function GetCalendarFolder(folder: OLEVariant): OLEVariant;
  var
    i: Integer;
  begin
    for i := 1 to folder.Count do
    begin
      if (folder.Item[i].DefaultItemType = olAppointmentItem) then
        Result := folder.Item[i]
      else
        Result := GetCalendarFolder(folder.Item[i].Folders);
      if not VarIsNull(Result) and not VarIsEmpty(Result) then
        break
    end;
  end;
var
  outlook, ns, folder, appointment: OLEVariant;
begin
  {initialize an Outlook}
  outlook := CreateOLEObject('Outlook.Application');
  {get MAPI namespace}
  ns := outlook.GetNamespace('MAPI');
  {get a default Contacts folder}
  folder := GetCalendarFolder(ns.Folders);
  {if Contacts folder is found}
  if not VarIsNull(folder) and not VarIsEmpty(folder) then
  begin
    {create a new item}
    appointment := folder.Items.Add(olAppointmentItem);
    {define a subject and body of appointment}
    appointment.Subject := 'new appointment';
    appointment.Body := 'call me tomorrow';
      {location of appointment}
      appointment.Location := 'room 3, level 2';
    {duration: 10 days starting from today}
    appointment.Start := Now() + 0.05;
    appointment.End := Now()+10; {10 days for execution}
    appointment.AllDayEvent := 1; {all day event}
    {set reminder in 20 minutes}
    appointment.ReminderMinutesBeforeStart := 20;
    appointment.ReminderSet := 1;
    {set a high priority}
    appointment.Importance := olImportanceHigh;
    {add a few recipients}
    appointment.Recipients.Add('person1@domain.com');
    appointment.Recipients.Add('person2@domain.com');
    {change an organizer name}
    appointment.Organizer := 'organizer@domain.com';
    {to save an appointment}
    appointment.Save;
    {to display an appointment}
    appointment.Display(True);
    {to print a form}
    appointment.PrintOut;
  end;
  {to free all used resources}
  folder := UnAssigned;
  ns := UnAssigned;
  outlook := UnAssigned
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:41:22 星期天 说道:

------------------------------------------------------------------------
--------
Sometime ago I posted a few tips for MS Outlook automation. I want to 
continue this serie.
If you tried to work with messages from Delphi, you know that received 
message have the SenderName property (name of sender) but doesn't 
allow to read the real address of sender. Something like SenderAddress 
is not available.
Exists a few methods to retrieve this information:
1. help file says that sender is in Recipients collection with Type 
property - 0 (olOriginator).
But this way is not work for any version of MS Outlook. So just 
iterate thru collection of Recipients and find an item with Type=0 
couldn't return required value
2. as alternative you can read a ReplyTo property - there you'll receive
 an address (but generally ReplyTo and Sender could be different).
For example, in messages which I send from own mail account these values
 are different.
3. to create a new MailItem (just will be destroyed without saving in 
end of work), define a Recipient as value which you received from 
SenderName of your original message and call a Resolve method - after 
that you'll recieve a correct email address of this sender.
4. more correct and fast solution is the next:
begin
  objCDO := CreateOLEObject('MAPI.Session');
  objCDO.Logon('', '', False, False);
  objMsg := objCDO.GetMessage(itemOL.EntryID, itemOL.Parent.StoreID);
   s := objMsg.Sender.Address;
   showmessage(s);
   objMsg := UnAssigned;
   objCDO := UnAssigned;
end
where itemOL is a MailItem which contain a SenderName but doesn't 
contain a SenderAddress:-)

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:41:33 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes in development if you use OLE automation of some object, 
your application will not work because application is not installed on 
client computer. For example, you use MS excel automation but MS Excel 
is not installed.
You can easy check if OLE object is installed and correctly registered 
using CLSIDFromProgID function (for MS Excel as example only):
var
  ClassID: TCLSID;
  strOLEObject: string;
begin
  strOLEObject := 'Excel.Application';
  if (CLSIDFromProgID(PWideChar(WideString(strOLEObject)), ClassID) = 
S_OK)
then
  begin
    <application is installed>
  end
  else
  begin
    <application is NOT installed>
  end
end;
In same manner you can check any other required OLE object.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:41:47 星期天 说道:

------------------------------------------------------------------------
--------
Anyone who worked with OLE automation, know that OLE is very slowly. 
Especially if you work using late binding (which have a lot of other 
advantages which early binding haven't)
A reason of bad performance is the next: every command (method or 
property) which you access (no matter in read or write mode) will be 
interpretated (a-la script). I mean that this command must be found in 
table of available methods/properties by string name and only if found,
 a physical memory address for execution will be calculated.
So if your code contain a lot of access to methods/properties, your code
 will be slow.
For example, you need transfer some data from Delphi application into 
xls-spreadsheet.
You can solve a task in two different ways (now I describe only late 
binding for OLE automation and don't describe another methods):
to navigate thru own data and export every data in required cell 
to prepare a variant array with copied data and apply this array with 
data into desired range of cells 
I must say that second method will be faster than first because you'll 
call less commands from OLE object and main code will be executed 
without OLE automation.
Small sample: to export some StringGrid into xls-file.
var
  xls, wb, Range: OLEVariant;
  arrData: Variant;
begin
  {create variant array where we'll copy our data}
  arrData := VarArrayCreate([1, yourStringGrid.RowCount, 1, 
yourStringGrid.ColCount], varVariant);
  {fill array}
  for i := 1 to yourStringGrid.RowCount do
    for j := 1 to yourStringGrid.ColCount do
      arrData[i, j] := yourStringGrid.Cells[j-1, i-1];
  {initialize an instance of Excel}
  xls := CreateOLEObject('Excel.Application');
  {create workbook}
  wb := xls.Workbooks.Add;
  {retrieve a range where data must be placed}
  Range := wb.WorkSheets[1].Range[wb.WorkSheets[1].Cells[1, 1],
                                  wb.WorkSheets[1].
Cells[yourStringGrid.RowCount, yourStringGrid.ColCount]];
  {copy data from allocated variant array}
  Range.Value := arrData;
  {show Excel with our data}
  xls.Visible := True;
end;
Of course, you must understand that such method is not good for large 
data arrays because to allocate in memory large array is not easy task.
 You must find some optimal size for data transfer (for example, to copy
 every 10 rows) and as result you'll receive an optimal code both for 
memory use and performance.
Anyway more faster way to transfer data is not use OLE at all:-) You can
 use my TSMExportToXLS component from SMExport suite for this task. 
There is implemented a direct xls-file creation which doesn't require 
installed MS Excel at all..

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:42:06 星期天 说道:

------------------------------------------------------------------------
--------
As you know I like MS Outlook because there a lot of possibilities and 
OLE automation of Outlook allow to solve possible any task.
Today I want continue a serie of tips for MS Outlook.
So how to read a collection of Contacts which are exist in MS Outlook?
This task is very popular. For example, you want to develop a sample 
tool which will notify you about birthday for someone or you want to 
send messages to "mailing list". So you want to naviagte thru list of 
defined contacts and process any item.
Below you'll find a sample code:
const
  olFolderContacts = $0000000A;
var
  outlook, NameSpace, Contacts, Contact: OleVariant;
  i: Integer;
begin
  outlook := CreateOleObject('Outlook.Application');
  NameSpace := outlook.GetNameSpace('MAPI');
  Contacts := NameSpace.GetDefaultFolder(olFolderContacts);
  for i := 1 to Contacts.Items.Count do
  begin
    Contact := Contacts.Items.Item(i);
    {now you can read any property of contact. For example, full name 
and
     email address}
    ShowMessage(Contact.FullName + ' <' + Contact.Email1Address + '>');
  end;
  Outlook := UnAssigned;
end;
if you need a birthday, you can retrieve it as DateToStr(Contact.
Birthday)
Any contact item have a lot of properties. See a list (alphabet):
Birthday
Business2TelephoneNumber
BusinessAddress
BusinessAddressCity
BusinessAddressCountry
BusinessAddressPostalCode
BusinessAddressPostOfficeBox
BusinessAddressState
BusinessAddressStreet
BusinessFaxNumber
BusinessHomePage
BusinessTelephoneNumber
CompanyAndFullName
CompanyMainTelephoneNumber
CompanyName
ComputerNetworkName
Department
Email1Address
Email1AddressType
Email1DisplayName
Email2Address
Email2AddressType
Email2DisplayName
Email3Address
Email3AddressType
Email3DisplayName
FirstName
FTPSite
FullName
FullNameAndCompany
GovernmentIDNumber
Hobby
Home2TelephoneNumber
HomeAddress
HomeAddressCity
HomeAddressCountry
HomeAddressPostalCode
HomeAddressPostOfficeBox
HomeAddressState
HomeAddressStree
HomeFaxNumber
HomeTelephoneNumber
Initials
ISDNNumber
JobTitle
Language
LastName
LastNameAndFirstName
MailingAddress
MailingAddressCity
MailingAddressCountry
MailingAddressPostalCode
MailingAddressPostOfficeBox
MailingAddressState
MailingAddressStreet
MiddleName
NickName
OfficeLocation
OrganizationalIDNumber
PersonalHomePage
PrimaryTelephoneNumber
Profession
Suffix
Title
WebPage

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:42:52 星期天 说道:

Windows API
#0001 How can I create a screenshot? 
#0004 How can I receive a list of processes running on a system? 
#0006 Delete a file into Recycle Bin 
#0007 To detect a Windows version 
#0009 To switch a keyboard layout 
#0011 To add a some forms in Windows Task Manager 
#0014 To get a system icons 
#0016 Turn monitor power on/off 
#0024 Turn the screensaver on/off 
#0027 The current computer and user names 
#0052 To get a sizes of some system controls 
#0053 To set the clock (system datetime) in Windows 
#0070 To read the environment string 
#0082 To read the current code page of system 
#0084 To load the icons from external dll/exe 
#0086 To register the file extention and context menu 
#0098 To retrieve a full path of module in DLL 
#0105 To refresh a desktop 
#0106 To read a path of system folder 
#0112 To press a mouse button from code 
#0131 To use extended Windows dialogs 
#0133 To read native text of current locale 
#0138 To change a color of standard TProgressbar 
#0140 To define a custom icon for some folder 
#0147 To show/hide clocks in tray 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:43:16 星期天 说道:

------------------------------------------------------------------------
--------
In the next code I show how you can save in the file the current desktop
 image: 
procedure TForm1.Button1Click(Sender: TObject);
var DCDesk: HDC; // hDC of Desktop
  bmp: TBitmap;
begin
  {Create a bitmap}
  bmp := TBitmap.Create;
  {Set a bitmap sizes}
  bmp.Height := Screen.Height;
  bmp.Width := Screen.Width;
  {Get a desktop DC handle - handle of a display device context}
  DCDesk := GetWindowDC(GetDesktopWindow);
  {Copy to any canvas, here canvas of an image}
  BitBlt(bmp.Canvas.Handle, 0, 0, Screen.Width, Screen.Height,
         DCDesk, 0, 0, SRCCOPY);
  {Save the bitmap}
  bmp.SaveToFile('ScreenShot.bmp');
  {Release desktop DC handle}
  ReleaseDC(GetDesktopWindow, DCDesk);
  {Release a bitmap}
  bmp.Free;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:43:26 星期天 说道:

------------------------------------------------------------------------
--------
Using next procedure you can display the text with any angle. 
Today I answered on question with same subject by e-mail to Marcus 
Monaghan and I sure, that this example will useful for someone else.
procedure TForm1.Button1Click(Sender: TObject);
var handler: THandle;
    data: TProcessEntry32;
  function GetName: string;
  var i:byte;
  begin
     Result := '';
     i := 0;
     while data.szExeFile[i] <> '' do
     begin
        Result := Result + data.szExeFile[i];
        Inc(i);
     end;
   end;
begin
  handler := CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
  if Process32First(handler, data) then
  begin
    listbox1.Items.Add(GetName());
    while Process32Next(handler, data) do
       listbox1.Items.Add(GetName());
   end
   else
      ShowMessage('Error');
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:43:36 星期天 说道:

------------------------------------------------------------------------
--------
uses ShellApi;
var FileOpStruc: TSHFileOpStruct;
    s: PChar;
begin
  s := 'C:\your_full_path\your_file_name.txt';
  with FileOpStruc do
  begin
    Wnd := 0;
    wFunc := FO_DELETE;
    pFrom := s;
    fFlags := FOF_ALLOWUNDO
  end;
  SHFileOperation(FileOpStruc);
end;
You can delete a files by some mask too. 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:43:44 星期天 说道:

------------------------------------------------------------------------
--------
var
  OSVersionInfo : TOSVersionInfo;
begin
  OSVersionInfo.dwOSVersionInfoSize := SizeOf(OSVersionInfo);
  if GetVersionEx(OSVersionInfo) then
    with OSVersionInfo do
    begin
      WinVerLabel.Caption := Format('%s%s%s%s%s%s%s%s',
                        ['Windows: ', IntToStr(dwMajorVersion), '.',
                         IntToStr(dwMinorVersion), ' (Build ',
                         IntToStr(dwBuildNumber), szCSDVersion, ')']);
      case dwPlatformId of
        0: WinPlatformLabel.Caption := 'Platform: Win32s on Windows 3.
1';
        1: WinPlatformLabel.Caption := 'Platform: Win32 on Windows 95';
        2: WinPlatformLabel.Caption := 'Platform: Windows NT';
      end;
    end;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:43:56 星期天 说道:

------------------------------------------------------------------------
--------
If you want to change a current keyboard layout, you can:
1. var lang: HKL;
    lang := LoadKeyboardLayout(pcKeyboard, 0);
    SetActiveKeyboardLayout(lang);
2. LoadKeyboardLayout(pcKeyboard, KLF_ACTIVATE);
where pcKeyboard is:
 '00000409' - english
 '00000419' - russian
 '00000422' - ukrainian
 '00000407' - german
 '0000040C' - french
 '00000410' - italian
 '00000416' - portuguese
 '0000040A' - spanish
... (for more information view a language consts in windows.pas)
------------------------------------------------------------------------
--------

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:44:04 星期天 说道:

------------------------------------------------------------------------
--------
By default, when you create the application in Delphi/C++Builder, in 
Windows Task Manager will be included a main form handle only. If you 
want to add the additional form, you must override the CreateParams 
procedure: 
procedure TForm1.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.ExStyle := Params.ExStyle or WS_Ex_AppWindow;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:44:47 星期天 说道:

------------------------------------------------------------------------
--------
If you want to fill in TImageList the system icon list, then you can 
write the next: 
var FileInfo: TSHFileInfo;
begin
  {to get a small icons}
  imgListSysSmall := TImageList.Create(nil);
  with imgListSysSmall do
  begin
    Handle := SHGetFileInfo('', 0, FileInfo, SizeOf(TSHFileInfo),
      SHGFI_SMALLICON or SHGFI_SYSICONINDEX );
   ShareImages := True;
  end;
  {to get a small icons}
  imgListSysLarge := TImageList.Create(nil);
  with imgListSysLarge do
  begin
    Handle := SHGetFileInfo('', 0, FileInfo, SizeOf(TSHFileInfo),
      SHGFI_LARGEICON or SHGFI_SYSICONINDEX );
   ShareImages := True;
  end;
end;
Also if you want to get a "My Computer" icon, you must use the next
function:
var FileInfo: TSHFileInfo;
     PIDL: PItemIDList;
begin
  SHGetSpecialFolderLocation(Application.Handle, CSIDL_DRIVES, PIDL);
  if SHGetFileInfo(PChar(PIDL), 0, FileInfo, SizeOf(TSHFileInfo),
     SHGFI_PIDL or SHGFI_SYSICONINDEX ) <> 0 then
        Result := FileInfo.iIcon;
end;
In this function you can use the constants from shlobj.pas:
  CSIDL_DESKTOP                       = $0000; 
  CSIDL_PROGRAMS                      = $0002; 
  CSIDL_CONTROLS                      = $0003; 
  CSIDL_PRINTERS                      = $0004; 
  CSIDL_PERSONAL                      = $0005; 
  CSIDL_FAVORITES                     = $0006; 
  CSIDL_STARTUP                       = $0007; 
  CSIDL_RECENT                        = $0008; 
  CSIDL_SENDTO                        = $0009; 
  CSIDL_BITBUCKET                     = $000a; 
  CSIDL_STARTMENU                     = $000b; 
  CSIDL_DESKTOPDIRECTORY              = $0010; 
  CSIDL_DRIVES                        = $0011; 
  CSIDL_NETWORK                       = $0012; 
  CSIDL_NETHOOD                       = $0013; 
  CSIDL_FONTS                         = $0014; 
  CSIDL_TEMPLATES                     = $0015; 
  CSIDL_COMMON_STARTMENU              = $0016; 
  CSIDL_COMMON_PROGRAMS               = $0017; 
  CSIDL_COMMON_STARTUP                = $0018; 
  CSIDL_COMMON_DESKTOPDIRECTORY       = $0019; 
  CSIDL_APPDATA                       = $001A; 
  CSIDL_PRINTHOOD                     = $001B;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:44:58 星期天 说道:

------------------------------------------------------------------------
--------
If you want, you can turn off or turn on the monitor power. Try it:
SendMessage(Application.Handle, WM_SYSCOMMAND, SC_MONITORPOWER, 0);
or
SendMessage(Application.Handle, WM_SYSCOMMAND, SC_MONITORPOWER, 1);

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:45:05 星期天 说道:

------------------------------------------------------------------------
--------
The following example shows how to turn the screensaver off.
1. turn off:
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, 0, nil, 0);
2. turn on:
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, 1, nil, 0);

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:45:15 星期天 说道:

------------------------------------------------------------------------
--------
To get a computer and user name you can so:
var ComputerName: array[1..20] of Char;
     UserName: array[1..512] of Char;
    arrSize: DWord;
begin
  arrSize := SizeOf(ComputerName);
  GetComputerName(@ComputerName, arrSize);
  arrSize := SizeOf(UserName);
  GetUserName(@UserName, arrSize);
  ShowMessage('Computer name is: ' + ComputerName + #13#10 +
                        'User name is: ' + UserName)
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:45:46 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes in run-time you must detect the sizes of some system controls.
 For example, width of scrollbar or height of form caption etc.
If you want to get this system value, you must call the Windows API's 
function GetSystemMetrics with different parameter. In result you'll get
 the wished. For example,
intFormCaptionHeight := GetSystemMetrics(SM_CYCAPTION)
As result, you in intFormCaptionHeight you have a height of normal 
caption area in pixels.
The full list of wished system metrics you can view in Windows API's 
help by GetSystemMetrics topic.
The some useful metrics:
SM_CMOUSEBUTTONS - number of buttons on mouse
SM_CXFULLSCREEN, SM_CYFULLSCREEN - width/height of the client area for 
full-screen window
SM_CXHSCROLL, SM_CYHSCROLL - width/height of the arrow bitmap on 
horizontal scrollbar
SM_CXSCREEN, SM_CYSCREEN - width/height of the screen
SM_CXSIZE, SM_CYSIZE - width/height of a button in window caption

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:45:56 星期天 说道:

------------------------------------------------------------------------
--------
If you need set the system date and time in MS Windows from your 
application, then you can use the next code:
function ChangeSystemDateTime(dtNeeded: TDateTime): Boolean;
var
  tzi: TTimeZoneInformation;
  dtSystem: TSystemTime;
begin
  GetTimeZoneInformation(tzi);
  dtNeeded := dtNeeded + tzi.Bias / 1440;
  with dtSystem do
  begin
    wYear := StrToInt(FormatDateTime('yyyy', dtNeeded));
    wMonth := StrToInt(FormatDateTime('mm', dtNeeded));
    wDay := StrToInt(FormatDateTime('dd', dtNeeded));
    wHour := StrToInt(FormatDateTime('hh', dtNeeded));
    wMinute := StrToInt(FormatDateTime('nn', dtNeeded));
    wSecond := StrToInt(FormatDateTime('ss', dtNeeded));
    wMilliseconds := 0;
  end;
  Result := SetSystemTime(dtSystem);
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:46:34 星期天 说道:

------------------------------------------------------------------------
--------
If you need read the environemnt strings, you must call the 2 API 
functions:
GetEnvironemtnStrings and FreeEnvironmentStrings:
var
  Env1, Env2: PChar;
  lstStrings: TStrings;
begin
  lstStrings := TStringList.Create;
  Env1 := GetEnvironmentStrings;
  Env2 := PEnv1;
  if Env2 <> nil then
    repeat
      lstStrings.Add(StrPas(Env2));
      inc(Env2, StrLen(Env2) + 1);
    until Env2^ = #0;
  FreeEnvironmentStrings(Env1);
  Env2 := nil;
  <...>
  lstStrings.Free
end;
The GetEnvironmentStrings function returns the address of the 
environment block for the current process. Each environment variable 
is null terminated. The set of strings is double null terminated. 
As alternative method, you can read a specified variable. The 
GetEnvironmentVariable function retrieves the value of the specified 
variable from the environment block of the calling process. The value is
 in the form of a null-terminated string of characters.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:46:51 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes in run-time you must detect the current values of code page.
To detect the code page of Windows operation system you must call the 
GetACP function from Windows API.
This function will return the value:
874 Thai 
932 Japan 
936 Chinese (PRC, Singapore) 
949 Korean 
950 Chinese (Taiwan, Hong Kong) 
1200 Unicode (BMP of ISO 10646) 
1250 Windows 3.1 Eastern European 
1251 Windows 3.1 Cyrillic 
1252 Windows 3.1 Latin 1 (US, Western Europe) 
1253 Windows 3.1 Greek 
1254 Windows 3.1 Turkish 
1255 Hebrew 
1256 Arabic 
1257 Baltic 
If you needs to read the code page of "DOS" sessions, you must call 
the GetOEMCP function from Windows API. This function will return the 
value:
437 MS-DOS United States 
708 Arabic (ASMO 708) 
709 Arabic (ASMO 449+, BCON V4) 
710 Arabic (Transparent Arabic) 
720 Arabic (Transparent ASMO) 
737 Greek (formerly 437G) 
775 Baltic 
850 MS-DOS Multilingual (Latin I) 
852 MS-DOS Slavic (Latin II) 
855 IBM Cyrillic (primarily Russian) 
857 IBM Turkish 
860 MS-DOS Portuguese 
861 MS-DOS Icelandic 
862 Hebrew 
863 MS-DOS Canadian-French 
864 Arabic 
865 MS-DOS Nordic 
866 MS-DOS Russian (former USSR) 
869 IBM Modern Greek 
874 Thai 
932 Japan 
936 Chinese (PRC, Singapore) 
949 Korean 
950 Chinese (Taiwan, Hong Kong) 
1361 Korean (Johab) 
Also you can check the valids of code page. For example,
if IsValidCodePage(866) then
  ShowMessage('Correct MS-DOS russian code page')

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:47:05 星期天 说道:

------------------------------------------------------------------------
--------
Exists a few ways to store the some icons in external files and load 
them in run-time. For example, the next code shows how you can load 
the icons from dll/exe file:
procedure TyourForm.btnFillIconsClick(Sender: TObject);
var
  i: Integer;
  pcDLLName: PChar;
begin
  pcDLLName := 'MORICONS.DLL';
  for i := 0 to ExtractIcon(Handle, pcDLLName, Cardinal(-1)) - 1 do
    ImageList_AddIcon(yourImageList.Handle, ExtractIcon(Handle, 
pcDLLName, i));
end;
This method is useful when you want to load the some system icons or 
from own dynamic library in which you placed the collection of icons.
If you needs to load the icons in run-time but you don't want to use the
 external file, you can place the glyph collection into resource file 
and link it in own exe-file. How to do it I'll show in the next tip.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:47:29 星期天 说道:

------------------------------------------------------------------------
--------
In the next example will be registered the file extention (.newext) - 
files of this type will open using MyApp.Exe application. Also will be 
registered the one default action and two additional items in context 
menu, assigned
with this file extention. 
Example:
uses
  Registry;
procedure TForm1.Button1Click(Sender: TObject);
begin
  with TRegIniFile.Create('') do
    try
      RootKey := HKEY_CLASSES_ROOT;
      WriteString('.newext', '', 'NewExt');
      WriteString('NewExt', '', 'Your description of NewExt files');
      WriteString('NewExt\DefaultIcon', '', 'C:\MyApp.Exe,0');
      WriteString('NewExt\Shell', '', 'This_Is_Our_Default_Action');
      WriteString('NewExt\Shell\First_Action', '','This is our first 
action');
      WriteString('NewExt\Shell\First_Action\command', '', 'C:\MyApp.Exe
 /LotsOfParamaters %1');
      WriteString('NewExt\Shell\This_Is_Our_Default_Action', '', 'This 
is our default action');
      WriteString('NewExt\Shell\This_Is_Our_Default_Action\command', '',
 'C:\MyApp.Exe %1');
      WriteString('NewExt\Shell\Second_Action', '', 'This is our 
second action');
      WriteString('NewExt\Shell\Second_Action\command', '', 'C:\MyApp.
Exe /TonsOfParameters %1');
   finally
      Free;
   end;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:48:00 星期天 说道:

------------------------------------------------------------------------
--------
If you develops the dll, in some tasks you must know the full path of 
this\par dll but not the path of application from which was ran this 
library.
For this task you must use the GetModuleFileName function. View a 
sample:
var
   DLLFileName: PChar;
begin
  GetMem(DLLFileName, MAX_PATH+1);
  if (DLLFileName <> nil) then
    GetModuleFileName(hInstance, DLLFileName, MAX_PATH);
...
end;
So in pDLLFileName variable you'll have the full path value.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:48:09 星期天 说道:

------------------------------------------------------------------------
--------
Yesterday I answered on this question in delphi programming mailing 
list. I want to post this answer as tip because I sure that this 
question can ask a lot of developers but to find an answer is not easy 
task.
So if you need refresh a desktop in run-time, you can execute a next 
procedure:
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
This code will refresh a desktop image and re-read the icons for files 
with registered extentions.
PS: you can do a same if you press a right mouse button on desktop and 
select Update item.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:48:22 星期天 说道:

------------------------------------------------------------------------
--------
Just now in delphi-programming I posted a code which allows to recieve a
 path to some system folder (StartUp, Recent Files etc). As you know, 
the path can be different on other computers (depends from operationg 
system, localization, user profile and user settings).
This code allows to read a current value:
uses ShlObj, ActiveX;
function GetSystemPath(Folder: Integer): string;
var
  PIDL: PItemIDList;
  Path: LPSTR;
  AMalloc: IMalloc;
begin
  Path := StrAlloc(MAX_PATH);
  SHGetSpecialFolderLocation(Application.Handle, Folder, PIDL);
  if SHGetPathFromIDList(PIDL, Path) then
    Result := Path;
  SHGetMalloc(AMalloc);
  AMalloc.Free(PIDL);
  StrDispose(Path);
end;
Now you can call this function with different parameters. For example:
CSIDL_DESKTOP for WINDOWS\Desktop
CSIDL_DESKTOPDIRECTORY for WINDOWS\Desktop
CSIDL_FONTS for WINDOWS\FONTS
CSIDL_NETHOOD for WINDOWS\NetHood
CSIDL_PERSONAL for X:\My Documents
CSIDL_PROGRAMS for WINDOWS\StartMenu\Programs
CSIDL_RECENT for WINDOWS\Recent
CSIDL_SENDTO for WINDOWS\SendTo
CSIDL_STARTMENU for WINDOWS\Start Menu
CSIDL_STARTUP for WINDOWS\Start Menu\Programs\StartUp
CSIDL_TEMPLATES for WINDOWS\ShellNew

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:48:44 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes you need press a left and/or right mouse button from code 
without any user activity. For example, as part of demo program which 
demonstrates the possibilities of your application. You can use the next
 procedures which simulates a mouse activity:
procedure PressMouseDown(IsLeftButton: Boolean);
begin
  if IsLeftButton then
    mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
  else
    mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0);
end;
procedure PressMouseUp(IsLeftButton: Boolean);
begin
  if IsLeftButton then
    mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)
  else
    mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);
end;
procedure ClickMouseButton(IsLeftButton: Boolean);
begin
  PressMouseDown(IsLeftButton);
  PressMouseUp(IsLeftButton);
end;
So if you need "click" on mouse button, call the ClickMouseButton 
procedure.
But if you need "press" on button, do a something and release a button,
 then call a PressMouseDown, execute the some own code and call the 
PressMouseUp.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:49:09 星期天 说道:

------------------------------------------------------------------------
--------
Today I want to show a few samples how you can use the extended 
dialogs from MS Windows (Find Files, Find Computer, Select Icon etc) 
in own code.
Usually the MessageDlg is most used from standard dialogs but inside 
of Windows you'll find a lot of other useful dialogs too.
The any such dialog is declared in the Shell32.dll library and you can 
use it so:
1. Select an icon
this dialog is a same which you'll see when you'll edit an icon of any 
lnk-file (icon on desktop, for example)
Declaration: 
function PickIconDlgA(OwnerWnd: HWND; lpstrFile: PAnsiChar; var 
nMaxFile: LongInt; var lpdwIconIndex: LongInt): LongBool; stdcall; 
external 'SHELL32.DLL' index 62; 
Example (icon of current application will be changed!):
procedure TForm1.Button4Click(Sender: TObject);
var
  FileName: array[0..MAX_PATH - 1] of Char;
  Size, Index: LongInt;
begin
  Size := MAX_PATH;
  FileName := 'c:\windows\system\shell32.dll';
  if PickIconDlgA(0, FileName, Size, Index) then
  begin
    if (Index <> -1) then
      Application.Icon.Handle := ExtractIcon(hInstance, FileName, 
Index);
  end;
end;
Of course, you can define any other file and in the dialog you'll see 
available icons of this executable file.
2. Find Computer
Declaration:
function SHFindComputer(pidlRoot: PItemIDList; pidlSavedSearch: 
PItemIDList): Boolean; stdcall; external 'Shell32.dll' index 91;
Example:
begin
  SHFindComputer(nil, nil);
end;
3. Find Files
Declaration:
function SHFindFiles(pidlRoot: PItemIDList; pidlSavedSearch: 
PItemIDList): Boolean; stdcall; external 'Shell32.dll' index 90;
Example:
begin
  SHFindFiles(nil,nil);
end;
Here the first parameter is a folder where you want to begin a search 
(nil is a Desktop). The second parameter allow to define a previous 
saved state of search process.
IMPORTANT:
Note that SHFindFiles and SHFindComputer are not modal dialogs (these 
dialogs will be started in separated thread) so the result of function 
will be True if dialog is created succesfully.
4. Shutdown dialog
Declaration:
procedure ExitWindowsDialog(ParentWnd: HWND); stdcall; external 
'Shell32.dll' index 60;
Example:
begin
  ExitWindowsDialog(0)
end;
5. RestartDialog
this dialog allow to ask end-user about Windows restarting and is used 
when changes are made to system that require a shutdown/restart before 
they will take effect.
Declaration:
function RestartDialog(ParentWnd: HWND; Reason: PAnsiChar; Flags: 
LongInt): LongInt; stdcall; external 'Shell32.dll' index 59;
Example:
begin
  if RestartDialog(0, 'I want to call a RestartDialog ', 
EW_RESTARTWINDOWS)
= IDYES then
    ShowMessage('succesfully started')
end;
You can define any reason of restarting (second parameter - additionally
 to default text or nil for default only) and use the another flag 
(one from the next available):
EWX_LOGOFF
EWX_SHUTDOWN
EWX_REBOOT
EW_RESTARTWINDOWS
EW_REBOOTSYSTEM
EW_EXITANDEXECAPP
This dialog is very useful for application which have embedded install 
procedure. 
6. OutOfSpace
Will display a notification dialog about "Out Of Space" for some defined
 drive.
Declaration:
procedure SHHandleDiskFull(Owner: HWND; Drive: UINT); stdcall; 
external 'Shell32.dll' index 185;
Example:
begin
  SHHandleDiskFull(0, 2);
end;
Note that second parameter is Drive number where 0 is A:, 1 is B:, 2 
is C: etc
Of course, in the Shell32.dll you'll find other dialogs too (Object 
Properties, Map Network Drive, Browse For Folder etc) and you can use 
these dialogs without any problems. 
IMPORTANT:
Don't forget to add ShlObj and ShellAPI units into uses-clause. 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:49:20 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes in application we want to show a text with description of 
current used locale.
For example, "english", "russian", "ukrainian", "chinese" etc
For this task you can use VerLanguageName function from Windows API:
var
  ID: LangID;
  LanguageName: array[0..255] of Char;
begin
  {read current system locale}
  ID := GetSystemDefaultLangID;
  {convert an ID into text}
  VerLanguageName(ID, LanguageName, SizeOf(LanguageName));
  ShowMessage(LanguageName);
end;
Of course, you can expand this code and to translate a locale as 
parameter
For example, if ID is $419, you'll receive a "russian". For $$0A - the 
"Spanish (Traditional Sort)", for $0C0C - the "French (Canadian)", for 
$407 - "German (Standard)" etc
Especially it is usefully when you'll retrieve an information from 
file (see GetFileVersionInfo/VerQueryValue functions or my freeware 
TSMVersionInfo component) or in some install tools.
Important
if ID is incorrect, you'll receive an "Language Neutral" as result 
text in result will be translated in the current language. I mean that 
text will be not in English if you have localized version of MS 
Windows 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:49:30 星期天 说道:

------------------------------------------------------------------------
--------
Standard TProgressbar component from Win32 palette doesn't allow to 
change a color of progress using properties but control from MS 
Windows allow to do it (TProgressbar is not pure VCL component - this is
 a wrapper for Windows control, the same as TEdit, TStaticText, 
TButton etc)
This feature can be easy added in run-time - just call the next 
procedure and define parameters - your Progressbar and desired color:
procedure ChangeProgressColor(pb: TProgressbar; cl: TColor);
const
  PBM_SETBARCOLOR = WM_USER+9;
begin
  inherited;
  SendMessage(pb.Handle, PBM_SETBARCOLOR, 0, cl); 
end;
For example:
ChangeProgressColor(pbStatus, clRed);

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:49:40 星期天 说道:

------------------------------------------------------------------------
--------
In MS Windows you can define any custom icon for desired folder 
instead default icon (yellow folder picture). Also you can define own 
string description which will be displayed in Explorer application.
To do it is very easy task:
1. you must create a desktop.ini file with special structure:
[.ShellClassInfo]
IconFile=yourFileWithIcon.ico
IconIndex=0
InfoTip=your description text
2. you must set a System flag to attributes of your folder
A first part is solved in Delphi in a few lines of code:
with TINIFile.Create(yourFolderName + '\desktop.ini') do
  try
    WriteString('.ShellClassInfo', 'IconFile', 'yourFileWithIco.ico');
    WriteString('.ShellClassInfo', 'IconIndex', '0'); //icon index
    WriteString('.ShellClassInfo', 'InfoTip', 'your description text');
    {flush a buffered INI-file to disk}
    UpdateFile; 
  finally
    Free
  end;
A second (to set a system attribute) is not hard too:
SetFileAttributes(PChar(yourFolderName), FILE_ATTRIBUTE_SYSTEM);

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:49:49 星期天 说道:

------------------------------------------------------------------------
--------
Today I want to publish a method for show/hide of clocks in traybar of 
MS Windows. This code will work with any version of Windows.
To hide clocks:
ShowWindow(FindWindowEx(FindWindowEx(FindWindow('Shell_TrayWnd', nil), 
0, 'TrayNotifyWnd', nil), 0, 'TrayClockWClass', nil), SW_HIDE);
To show clocks:
ShowWindow(FindWindowEx(FindWindowEx(FindWindow('Shell_TrayWnd', nil), 
0, 'TrayNotifyWnd', nil), 0, 'TrayClockWClass', nil), SW_SHOW);
Don't know why you need it but just for fun:-)

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:50:22 星期天 说道:


────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:51:04 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes in run-time you need play with colors. For example, you have 
the some color value and you want to make it more dark or light. The 
next two function were written for it.
function Dark(Col: TColor; Percent: Byte): TColor;
var R, G, B: Byte;
begin
  R := GetRValue(Col);
  G := GetGValue(Col);
  B := GetBValue(Col);
  R := Round(R*Percent/100);
  G := Round(G*Percent/100);
  B := Round(B*Percent/100);
  Result := RGB(R, G, B);
end;
function Light(Col: TColor; Percent: Byte): TColor;
var R, G, B: Byte;
begin
  R := GetRValue(Col);
  G := GetGValue(Col);
  B := GetBValue(Col);
  R := Round(R*Percent/100) + Round(255 - Percent/100*255);
  G := Round(G*Percent/100) + Round(255 - Percent/100*255);
  B := Round(B*Percent/100) + Round(255 - Percent/100*255);
  Result := RGB(R, G, B);
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:51:26 星期天 说道:

------------------------------------------------------------------------
--------
If you want to draw a something in the title bar of the form, you must 
handle the WM_NCPAINT message. For example in the next code I show how 
you can output the form caption with italic font style:
type
  TForm1 = class(TForm)
  private
     procedure WMNCPaint(var Msg: TWMNCPaint); message WM_NCPAINT;
  end;
var Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.WMNCPaint(var Msg: TWMNCPaint);
var ACanvas: TCanvas;
    intLeftShift, intTopShift: Integer;
begin
  inherited;
  ACanvas := TCanvas.Create;
  try
    {to retrieve the device context for the Form1 window}
    ACanvas.Handle := GetWindowDC(Form1.Handle);
    with ACanvas do
    begin
      Brush.Color := clActiveCaption;
      Font.Style := [fsItalic];
      {calculate the left coordinate for caption drawing}
      intLeftShift := GetSystemMetrics(SM_CYMENU) + 
GetSystemMetrics(SM_CXBORDER);
      {calculate the top coordinate for caption drawing}
      intTopShift := (GetSystemMetrics(SM_CYCAPTION) - Abs(Font.Height))
 div 2 + 1;
      {output the caption string}
      TextOut(intLeftShift, intTopShift, Caption)
    end;
  finally
    {to release the device context}
    ReleaseDC(Form1.Handle, ACanvas.Handle);
    ACanvas.Free
  end;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:51:36 星期天 说道:

------------------------------------------------------------------------
--------
If you need retrieve the value of the vertical refresh rate of the 
your monitor, you can call the GetDeviceCaps API's function with 
VREFRESH parameter.
This feature are available on Windows NT platform only, so before 
using you must check the Windows version. For example,
var ov: TOSVersionInfo;
     vr: Integer;
begin
  ov.dwOSVersionInfoSize := SizeOf(ov);
  GetVersionEx(ov);
  if OV.dwPlatformID = VER_PLATFORM_WIN32_NT then
  begin
    vr := GetDeviceCaps(Form1.Canvas.Handle, VREFRESH);
    Label.Caption := 'Frequence: ' + IntToStr(vr) + ' Hz';
  end;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:51:47 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes you need draw the chart on the own canvas. For example, you 
want to print a chart or include the chart in own bitmap. You can make 
it so:
1. yourChart.DrawToMetaCanvas(ACanvas, ARect);
or
2. 
var intChartWidth, intChartHeight: Integer;
    Meta: TMetaFile;
begin
  Meta := yourChart.TeeCreateMetafile(False, Rect(0, 0, intChartWidth, 
intChartHeight));
  try
    ACanvas.StretchDraw(ARect, Meta);
  finally
    Meta.Free;
  end;
end;
, where
- yourChart is a "source" chart component
- ARect is a rect in which you want to place a chart image,
- ACanvas is a "target" canvas
In the SMReport Designer I used the first procedure for chart 
outputing on the report band.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:52:01 星期天 说道:

------------------------------------------------------------------------
--------
In some applications you must draw the line where each pixel is some 
shape. The MS Windows allows to draw line of such type - you must use 
the LineDDA funtion. This function needs a callback function that is 
called for each pixel drawing. Of course, in this callback function 
you can realize the any drawing routines.
For example, the next code allows to draw the ellipse every 10 pixels:
var
  i: Integer;
procedure DrawEllipse(X, Y: Integer; lpData: LParam); stdcall;
implementation
procedure DrawEllipse(X, Y: Integer; lpData: LParam);
begin
  with TObject(lpData) as TForm do
  begin
    if (i mod 10 = 0) then 
      Canvas.Ellipse(x-5, y-5, x+5, y+5);
    Inc(i);
  end;
end;
procedure TForm1.FormPaint(Sender: TObject);
begin
  i := 0;
  LineDDA(0, 0, Width, Height, @DrawEllipse, Integer(yourForm));
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:52:09 星期天 说道:

------------------------------------------------------------------------
--------
Today I found a nice tip about changing the captions in standard dialogs
 (TOpenDialog or TSaveDialog).
By default the TOpenDialog have the two buttons: Open and Cancel. You 
can change this default value:
procedure TForm1.OpenDialog1Show(Sender: TObject);
begin
  SetDlgItemText(GetParent(OpenDialog1.Handle), IDOK, PChar('New 
&Open'));
  SetDlgItemText(GetParent(OpenDialog1.Handle), IDCANCEL, PChar('New 
&Cancel'));
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:52:17 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes in development you must to detect the current position of 
mouse. You can use the GetCursorPos function.
In the next example I shows how to popup the some TPopupMenu component 
in point, where user click a mouse button:
var
  P: TPoint;
begin
  GetCursorPos(P);
  yourPopupMenu.Popup(P.X, P.Y);
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:52:34 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes in development you must use the some values (for example, 
integer or strings) as data to each item in list (TStrings).
For example, you have the listbox with country name but you want to link
 a code value for each item. Or other example: you have a combobox 
with colors and you needs to link a color value to each colored line.
These tasks can be easy solved - the TStrings type have:
- the Items property: the string items which will be displayed in 
combobox or listbox
- the Objects property: the data which can be linked to each visible 
item.
Each Objects[i] is the pointer to some external data.
In this tip I want to explain how you can use this Objects property 
for storing of some additional info.
1. to store the integer values:
with qryGroups do
begin
  First;
  while not Eof do
  begin
    cbGroups.Items.AddObject(FieldByName('Name').AsString,
                             TObject(FieldByName('ID').AsInteger));
    Next;
  end;
end;
to read the integer value from selected item:
strID := LongInt(cbGroups.Items.Objects[cbGroups.ItemIndex]);
Comments: in this example, I used the type convertion - each pointer 
is address. But address is an integer value so we can place to item data
 the "virtual" address.
2. to store the string values:
with qryGroups do
begin
  First;
  while not Eof do
  begin
    cbGroups.Items.AddObject(FieldByName('Name').AsString,
                   TObject(LongInt(NewStr(FieldByName('ID').
AsString))));
    Next;
  end;
end;
to read a value from selected item:
strID := PString(cbGroups.Items.Objects[cbGroups.ItemIndex])^;
Comments: in this example I used the address on string which I created 
(see a help topic on NewStr).
Also don't forget that you must destroy the objects data which you 
created in this example. For exampe, you can do it in OnDestroy event:
for i := 0 to cbGroups.Items.Count-1 do
  DisposeStr(PString(cbGroups.Items.Objects[i]));

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:53:02 星期天 说道:

------------------------------------------------------------------------
--------
If you needs to hide the own application from Windows Taskbar, you can 
use the next procedure:
procedure TForm1.FormCreate(Sender: TObject);
begin
  ShowWindow(Application.Handle, SW_HIDE);
  SetWindowLong(Application.Handle, GWL_EXSTYLE,
          GetWindowLong(Application.Handle, GWL_EXSTYLE) or 
WS_EX_TOOLWINDOW);
  ShowWindow(Application.Handle, SW_SHOW);
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:53:10 星期天 说道:

------------------------------------------------------------------------
--------
If you needs to hide/show the Start button from Windows Taskbar, you can
 use the next procedure:
procedure HideShowStartButton(boolVisible: Boolean);
var
  Tray, Child: hWnd;
  C: array[0..127] of Char;
  S: string;
begin
  Tray := FindWindow('Shell_TrayWnd', nil);
  Child := GetWindow(Tray, GW_CHILD);
  while Child <> 0 do
  begin
    if GetClassName(Child, C, SizeOf(C)) > 0 then
    begin
      S := StrPas(C);
      if UpperCase(S) = 'BUTTON' then
      begin
        startbutton_handle := child;
        ShowWindow(Child, Integer(boolVisible))
      end;
    end;
    Child := GetWindow(Child, GW_HWNDNEXT);
  end;
end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:53:27 星期天 说道:

------------------------------------------------------------------------
--------
Today I found a good example how we can add a some control in standard 
MessageDialog.
For example, we have a some dialog for confirmation but user can check a
 checkbox (Don't show this message again) and in the next time this 
dialog will not show.
The idea of realization in the next:
1. we must create a dialog using CreateMessageDialog
2. this function will return a form object with dialog
3. in this object we can add a checkbox
4. show diallog using ShowModal
5. to check a result and process a state of our checkbox
6. to destroy a created checkbox and dialog object
View a source:
procedure TForm1.Button1Click(Sender: TObject); 
var 
  AMsgDialog: TForm; 
  ACheckBox: TCheckBox; 
begin 
  AMsgDialog := CreateMessageDialog('This is a test message.', 
mtWarning,
[mbYes, mbNo]); 
  ACheckBox := TCheckBox.Create(AMsgDialog); 
  with AMsgDialog do 
    try 
      Caption := 'Dialog Title' ; 
      Height := 169; 
      with ACheckBox do 
      begin 
        Parent := AMsgDialog; 
        Caption := 'Don''t show me again.'; 
        Top := 121; 
        Left := 8; 
      end; 
      if (ShowModal = ID_YES) then
      begin
        if ACheckBox.Checked then 
        begin 
          //... 
        end; 
        //... some additional processing
      end; 
    finally 
      ACheckBox.Free; 
      Free; 
    end; 
end; 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:53:41 星期天 说道:

--------
If you uses the TTreeView and/or TListView from Win32 page of default 
component palette, then you must know that if you have the large 
amount nodes, you have a very bad performance...
Of course, at same moment you'll try to find a some other third-party 
component that allow to work with your very large data but I want to 
give you the few hints which allows to increase a performance without 
any third-party components. Only using of optimized code.
Tip1:
if you need add a lot of nodes in same time (for example, after button 
click to load the 10000 nodes in tree from some source) then you must 
call:
yourTreeView.Items.BeginUpdate;
<your code for append of nodes>
yourTreeView.Items.EndUpdate;
This constuction will disable a repainting when you append the nodes - 
it's save a lot of time!
Tip2:
if you uses the some navigation by nodes, you must use the GetFirst 
and GetNext methods instead Items[i] using!
For example,
for i := 0 to yourTreeView.Items.Count-1 do
begin
  node := yourTreeView.Items[i];
  <process a node>
end;
For example, in own warehouse system I have a treeview with 5000 nodes 
which I load from Oracle8i resultset. After applying of these tips, 
the time of execution of procedure with append was decreased from 4-5 
minutes to 15-20 seconds! Trust me:-)
I don't sure but I think that it's a bad work of Borland when team 
developed the envelope for Win's treeview/listview. But maybe I'm 
wrong.
PS: of course, if you have a very-very large data with few billions of 
nodes or after applying of tips above all the same you have a bad 
performance, you must use the virtual mode of control or really select 
the other third-party control. But I suggest to change the your inteface
 logic - to navigate thru few billions of nodes in same time is very 
hard task for user! Not only for you:-)

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:53:54 星期天 说道:

------------------------------------------------------------------------
--------
Today I want to describe a method of dll-using. Of course, for some 
advanced users this tip is not useful but I hope that somebody will find
 it as interested.
In Delphi you have two mechanism of dll-loading: static and dynamic.
- the static method is when your application will be failed when dll 
is not present. Inside of application you'll define a function as 
external and in any code you'll use a function from dll as usual 
"native" function.
- the dynamic method allow to have a control under dll-load. For 
example, you can check an dll-existing and show a warning if dll is 
not exist (or try to load another dll, for example). Also to call a some
 function from such
> loaded dll, is more difficult in coding.
Small example.
Imagine that you have a dll with ExecScript procedure which have two 
parameters (UserID and ScriptName).
In static method you must declare a function as:
procedure ExecScript(UserID: Integer; ScriptName: PChar); stdcall; far;
 external 'yourDLLName.DLL';
and in the code you can call it as:
ExecScript(5, 'LogToSystem');
and it's all.
For dynamic mechanism you must:
 {define a procedure type with required parameters of your procedure 
in the DLL}
 type
   TDLLFunc = procedure(param1: Integer; param2: PChar);
 {assign a nil - not loaded function}
 const
   DLLFunc: TDLLFunc = nil;
 {handle of loaded dll}
 var
  DLLHandle: THandle;
 { load a library }
 DLLHandle := LoadLibrary(DLLName);
 if (DLLHandle < HINSTANCE_ERROR) then
     raise Exception.Create(DLLName + ' library can not be loaded or not
 found. ' + SysErrorMessage(GetLastError));
   try
     { load an address of required procedure}
     @DLLFunc := GetProcAddress(DLLHandle, 'ExecScript');
     {if procedure is found in the dll}
     if Assigned(DLLFunc) then
       DLLFunc(5, 'LogToSystem');
   finally
     {unload a library}
     FreeLibrary(DLLHandle);
   end;

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:54:10 星期天 说道:

------------------------------------------------------------------------
--------
Sometimes from code you want to find out if MS Windows is operating in 
saylight saving time. For example, I used this code in own GroupMail 
because in message header you must include a timestamp.
The code below allow to solve this task:
uses Windows;
const
  TIME_ZONE_ID_UNKNOWN  = 0;
  TIME_ZONE_ID_STANDARD = 1;
  TIME_ZONE_ID_DAYLIGHT = 2;
var
  tz : TTimeZoneInformation;
begin
  case GetTimeZoneInformation(tz) of
    TIME_ZONE_ID_STANDARD: ShowMessage(tz.StandardName);
    TIME_ZONE_ID_DAYLIGHT:  ShowMessage(tz.DaylightName);
  else
    ShowMessage('Unknown state');
  end;
end;
Also I want to say that similar code you can use for BIAS reading 
(offset to UTC/GMT in minutes of local time zone):
function GetTimeZoneBias: Integer;
var
  tz: TTimeZoneInformation;
begin
  case GetTimeZoneInformation(tz) of
    TIME_ZONE_ID_STANDARD: Result := -(tz.StandardBias + tz.Bias) div 
(24*60);
    TIME_ZONE_ID_DAYLIGHT: Result := -(tz.DaylightBias + tz.Bias) div 
(24*60);
  else
    Result := 0;
  end;
end;
If the result is 0 then time is a GMT. Else you'll receive a 
difference as positive or negative value.

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日17:54:25 星期天 说道:

------------------------------------------------------------------------
--------
Every developer during programming solved such task: you have some 
longtime data processing when user must see a progress but can do 
nothing.
By default (without any additional steps) during data processing your 
application will be frozen. The easiest way to solve it is to send 
Application.ProcessMessages from own loop but in this case the user 
could click some controls (by mouse or keyboard).
The method below will show how you can disable any mouse/keyboard 
actions:
you may assign a handler to Application.OnMessage:
Application.OnMessage := yourOnMessageHandler
where
procedure TForm1.yourOnMessageHandler(var Msg: TMsg; var Handled: 
Boolean);
begin
  case Msg.Message of
    WM_KEYFIRST..WM_KEYLAST, WM_MOUSEFIRST, WM_MOUSELAST:
      Handled := True
  end;    
end;
Very important: you must remove a handler when your data processing is 
completed (or in OnDestroy event of main form)
A basic scheme of data processing:
begin
  Application.OnMessage := yourOnMessageHandler;
<your long calculation>
  while <...> do
  begin
    <your calculations>
    <update a progressbar/statistic>;
    Application.ProcessMessages
  end;
  Application.OnMessage := nil;
end;
Of course, you can filter a mouse and/or keyboard by some own conditions
 - just expand a body of yourOnMessageHandler procedure...

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日20:03:57 星期天 说道:

------------------------------------------------------------------------
--------
1. Never create a separated function for checking of registration. 
Always place the checking code in some important procedure that is 
required for your program. If cracker will disable this procedure, you 
program will not work correctly.
2. Never give a meaningful name for procedure that contains a checking 
(like IsValidSerialNumber, IsTrialExpired or IsValidUser). Change a F2CA
 name and don't forget about rule#1.
3. If your code returned a message that is not valid serial number or 
username, then don't try in same moment to show a warning dialog. Save 
this information somewhere and show it later (better after few days).
4. Have a few different algorithms of validation. I mean that you can 
generate an activation code using two-three-: algorithms. It allows 
validating your code in few procedures. If cracker will find a 
validation procedure then he'll find an one part of validation only. You
 can check a different part of serial number in other days of month or 
better to activate this validation after few days after first validation
 receipt. In this case cracker will "find" a code and publish it but 
your program will works a few days only. To trace all code of program or
 spend a lot of time on your program is hard task for any cracker.
5. If you used a some encrypt method for generation of serial number 
from username, then include a pause in few seconds after username 
entry before processing. If cracker will use a brute force cracking, 
this task will take a lot of additional time and in pair with good 
encrypt method the task of cracking will be unreal.
6. Don't use short keys. Of course, it depends from crypt algorithm 
but your key must be long. Better to generate a few Kb of key (don't 
forget to send instruction about key entering) than use a 
key-generator for 10 characters with standard literal symbols.
7. Don't store algorithm of key generation in your code. It is bad 
technology when user will type the name and serial number and after that
 you will run own algorithm for calculation of serial from user name 
(for next compare). Better to compare some hash and magic structure. 
Other the cracker will create the own key-generator without any patch of
 your original exe-file.
8. Store the serial numbers or check sums in different places. Also 
don't forget that these places must be hidden or with hard access. For 
example, date/time of file creation, additional invisible line of pixels
 in bitmap, first letters of records in database etc. Also don't 
forget about unique algorithms for encryption of these secure codes - 
don't use the one algorithm for any hidden places.
9. Don't use the trivial crypt algorithms. The XOR is not good choice. 
Better to use MD5, RSA, BlowFish, GOST etc
10. If your trial software must be available in 30 days only, then don't
 try to use a system date-time. Better to use a date-time of some system
 files (SYSTEM.DAT or DA0, BOOTLOG.TXT, AUTOEXEC.BAT etc). Also don't 
forget that additionally you can create the some own hidden file 
during installation and use a date-time of this file.
11. Encrypt the all string resources that will show that program is 
evaluation or time is expired. You must decrypt it in run-time and/or 
build dynamically.
12. To lead cracker up the path garden: add the some dummy code with 
strange external calls in validation procedure. It will increase time of
 cracking because cracker must found a gold mine in the rubbish.
13. If you're expert in development, try to encrypt the some parts of 
code in exe/dll-file and decrypt it in run-time. Use a different methods
 of code checking - checksums, own labels...
14. As I wrote in rule#2 try to use a non-readable names of procedures 
(D1FA12, B123 etc) or use a names like system functions - GetWindow, 
MOUSE_EVENT_A, KeyboardLayoutW etc. Also you can use the names of some 
procedures which are popular in your programming language (of course, if
 this language support the one name for few different procedures in 
different units/modules).
15. If your programming language supports it, then use the run-time 
assigning of events. For example, in Borland Delphi/C++Builder you can 
write a validation code in procedure which will be assigned to OnClick 
event of some button. It's bad because name of this procedure will be 
stored in resources and can be removed in any resource editor. Better to
 assign a procedure to event in run-time.
16. If you can, don't disable the some extended features in trial 
version. Better to remove it and after registration customer will 
receive the other full program (protected, of course).
17. Frequently release a new versions or updates. Of course, don't 
forget to change the validation method (at least one) in each release.
You must understand that any software can be cracked. And any software 
will be cracked if your program is good and cost of cracking time is 
less than your license fee.
If you found that your software cracked, don't panic. In any situation 
you must find the good news for you. In this situation you must 
understand that your software is best on market but cracker which 
broke you protection are better then you now. It is like game - you 
released a new protection, he must crack it. The next step is your: 
you must change a protection and add a some hidden surprise for your 
opponent - new crypt algorithm, new hidden places etc. It's a life.
Additionally
Also I want to say the next: the protection is very important part of 
development process but don't spend a lot of time on it. If you'll 
concentrate on protection but you'll forget about program possibilities,
 your software will be not cracked forever. But reason is not a good 
protectionJ Your software must worth a time that you spent on 
protection.
The end-user need a good solution of own problem with good support and 
new features and your protection must be in background. Exist a lot of 
protections on hardware keys or new method of confirmation via 
Internet but these methods will disturb your customers. In this case you
 can lose them - they will select the other program without any choice 
for your.
And don't forget about privacy of your customer - you can't select the 
some personalized data without permissions. No matter that you need it 
for own protection. 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日21:47:03 星期天 说道:

This Delphi Tip checks to see if there is a disk in the disk drive,
first this function sets the errors off so that we don't receive an 
error like: "Disk not ready.". Then the function checks the size of 
the disk, if this is -1 then the function will result false, and the 
disk is not ready. 
Code: 
function DiskInDrive(Drive: Char): Boolean;
var
ErrorMode: word;
begin
  { make it upper case }
  if Drive in ['a'..'z'] then Dec(Drive, $20);
   { make sure it's a letter }
  if not (Drive in ['A'..'Z']) then
    raise EConvertError.Create('Not a valid drive ID');
   { turn off critical errors }
   ErrorMode := SetErrorMode(SEM_FailCriticalErrors);
   try
     { drive 1 = a, 2 = b, 3 = c, etc. }
     if DiskSize(Ord(Drive) - $40) = -1 then
      Result := False
     else
      Result := True;
   finally
    { restore old error mode }
    SetErrorMode(ErrorMode);
   end;
 end;
This source code is written by Robert Jones. 

────────────────────────────────────────
 CF (随风漂)                          于 2002年12月29日22:06:43 星期天 说道:

 ? How to navigate the taskbar without a mouse (Windows tip) 
Want to know if the Windows taskbar's auto hide feature is enabled? 
Here's a simple function to check this written in C Language / C++ and 
Delphi. 
Delphi CODE 
uses ShellAPI;
(*
  Returns TRUE if taskbar auto hide is on.
  if(IsTaskBarautoHideOn)then
  begin
    // auto hide is ON
  end;
*)
function IsTaskbarAutoHideOn : boolean;
var
  ABData : TAppBarData;
begin
  ABData.cbSize := sizeof(ABData);
  Result :=
    (SHAppBarMessage(ABM_GETSTATE, ABData)
     and ABS_AUTOHIDE) > 0;
end;
Listing #1 : Delphi code. Right click tbah_pas.pas to download. 
C Language / C++ CODE 
#include<shellapi.h>
//
// Returns >0 if taskbar auto hide is on.
//
int IsTaskbarAutoHideOn()
{
  APPBARDATA ABData;
  ABData.cbSize = sizeof(ABData);
  return
    SHAppBarMessage(ABM_GETSTATE, &ABData)
    & ABS_AUTOHIDE;
}

────────────────────────────────────────
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:1,884.693毫秒