2007. augusztus 31., péntek
Animate a form when minimizing or restoring it
Problem/Question/Abstract:
When I minimize a progam written in Delphi there's no animation on minimizing and restore. How can I enable this?
Answer:
In FormShow:
var
RecS, RecL: TRect;
begin
RecS := Rect(Screen.Width, Screen.Height, Screen.Width, Screen.Height);
RecL := ThisForm.BoundsRect;
DrawAnimatedRects(GetDesktopWindow, IDANI_CAPTION, RecS, RecL);
{ ... }
end;
In FormHide:
var
RecS, RecL: TRect;
begin
HideTimer.Enabled := False;
RecS := Rect(Screen.Width, Screen.Height, Screen.Width, Screen.Height);
RecL := ThisForm.BoundsRect;
DrawAnimatedRects(GetDesktopWindow, IDANI_CAPTION, RecL, RecS);
end;
2007. augusztus 30., csütörtök
How to split a string when the substrings are separated by more than one space character
Problem/Question/Abstract:
I have a lot of the following lines:
02-07-01 12:05:30 XXX AAAAAA 100 BBBBB 666666 300
3700 -555.00 4.00
The only way to separate those items in the string is if there are more than 2 spaces between the items. There can also be 3 to 5 spaces actually. If there is one space between strings - it is probably part of the bigger item as shown above: 'AAAAAA 100 BBBBB'. What would be the simplest way to split this string? I looked at delimitedText but I am not sure of if it is going to help me.
Answer:
Solve 1:
See routine below. Is this data you have in a fixed-width column format produced by another program? If so you cannot count on fields being separated by more than one space! In fact you may have cases where there is no space between the fields because a value fills the whole field width! For such files you have to use a different strategy to parse the lines.
{SplitDataString:
Dissect a string of items separated by more than one space character
Param S contains the string to split, param list takes the items obtained from S
Precondition: list <> nil
Description:
An item cannot start or end with a space but it may contain space characters flanked
by non-space characters. The routine does not support multibyte character sets as it
is implemented now.
Created 28.7.2002 by P. Below}
procedure SplitDataString(S: string; list: TStrings);
var
startindex: Integer;
function HasNextItem: Boolean;
begin
{We do not support a "Item" starting with a space!}
while (startindex <= Length(S)) and (S[startindex] = #32) do
Inc(startindex);
Result := startindex <= Length(S);
end;
function GetNextItem: string;
var
endindex: Integer;
begin
for endindex := startindex + 1 to Length(S) do
begin
if S[endindex] = ' ' then
if S[endindex + 1] = ' ' then
begin
{found end of a Item}
Result := Copy(S, startindex, endindex - startindex);
startindex := endindex + 2;
Exit;
end;
end;
{If we end here Item is the last in S}
Result := Copy(S, startindex, maxint);
startindex := Length(S) + 1;
end;
begin
Assert(Assigned(list));
{remove whitespace from start and end of string}
S := Trim(S);
startindex := 1;
while HasNextItem do
list.Add(GetNextItem);
end;
Example of use:
procedure TForm1.Button1Click(Sender: TObject);
begin
memo1.clear;
SplitDataString('02-07-01 12:05:30 XXX AAAAAA 100 BBBBB 666666 300 ' + '3700 -555.00 4.00', memo1.lines);
end;
Solve 2:
function SepSpacedOutStr(s: string): string;
var
i, x: integer;
begin
s := SysUtils.Trim(s);
if s <> '' then
begin
SetLength(result, Length(s));
x := 0;
i := 1;
while i <= Length(s) do
begin
if (s[i] <> #32) or ((i < Length(s)) and (s[i + 1] <> #32)) then
begin
Inc(x);
result[x] := s[i];
end
else
begin
if (i < Length(s)) and (s[i + 1] = #32) then
begin
Inc(x);
result[x] := ',';
Inc(x);
result[x] := #32;
while (i < Length(s)) and (s[i + 1] = #32) do
Inc(i);
end;
end;
Inc(i);
end;
SetLength(result, x);
end
else
result := '';
end;
2007. augusztus 29., szerda
Adding an item to the main menu or the tools menu
Problem/Question/Abstract:
You need to add an item into Delphi's IDE for your expert?
Answer:
Here is how I did it for TMultiLang..
If you do not want to use the tools menu, but instead create a new main menu entry, you need to replace the
menMain.FindMenuItem ()
with
menMain.InsertItem(8, '&Test', 'TestMainMenuItem', '', 0, 0, 0, [mfVisible, mfEnabled], nil);
taken from TMultiLang source code
constructor TMultiLangExpert.Create;
var
menMain: TIMainMenuIntf;
menToolsGallery: TIMenuItemIntf;
menToolsMenu: TIMenuItemIntf;
begin { Create }
inherited Create;
menMain := ToolServices.GetMainMenu; // get the IDE's main menu
if Assigned(menMain) then
try
// get the
2007. augusztus 28., kedd
Printing a Memo
Problem/Question/Abstract:
I have a simple editor unit with a TMemo component whose text I want to send to the printer. How can I do this?
Answer:
This is actually much easier that most people think, though you can get pretty fancy. With the procedure that I'll show you below, I will take advantage of the TMemo's Lines property, which is of type TStrings. The procedure will parse each line in the memo, and use Canvas.TextOut to print to the printer. After you see this code, you'll see how simple it is. Let's take a look at the code:
procedure PrintTStrings(Lst: TStrings);
var
I, Line: Integer;
begin
I := 0;
Line := 0;
Printer.BeginDoc;
for I := 0 to Lst.Count - 1 do
begin
Printer.Canvas.TextOut(0, Line, Lst[I]);
{Font.Height is calculated as -Font.Size * 72 / Font.PixelsPerInch which returns
a negative number. So Abs() is applied to the Height to make it a non-negative
value}
Line := Line + Abs(Printer.Canvas.Font.Height);
if (Line >= Printer.PageHeight) then
Printer.NewPage;
end;
Printer.EndDoc;
end;
Basically, all we're doing is sequentially moving from the beginning of the TStrings object to the end with the for loop. At each line, we print the text using Canvas.TextOut then perform a line feed and repeat the process. If our line number is greater than the height of the page, we go to a new page. Notice that I extensively commented before the line feed. That's because feeding a line was the only tricky part of the code. When I first wrote this, I just added the Font height to the line, and thus the code would generate a smaller and smaller negative number. The net result was that I'd only print one line of the memo. Actually TextOut would output to the printer, but it essentially printed from the first line up, not down. So, after carefully reading the help file, I found that Height is the result of the calculation of a negative font size, so I used the Abs() function to make it a non-negative number.
For more complex operations, I suggest you look at the help file under Printer or TPrinter, and also study the TextOut procedure. Now, what is Printer? Well, when you make a call to Printer, it creates a global instance of TPrinter, which is Delphi's interface into the Windows print functions. With TPrinter, you can define everything which describes the page(s) to print: Page Orientation, Font (through the Canvas property), the Printer to print to, the Width and Height of the page, and many more things.
2007. augusztus 27., hétfő
Extract the associated icon of an application
Problem/Question/Abstract:
In my program, I am displaying a set of path and file names on the screen as the user choses them using a TList box. Alongside this list box I would like to display the icon associated with each one (if available). I understand that some icons are embedded in the executable and others are associated by Windows. Can you point me in the right direction for determining what the icon is, programmatically extracting it and placing it into an image array.
Answer:
procedure TForm1.Button1Click(Sender: TObject);
var
HIcon: THandle;
iIcon: Word;
FilePath: array[0..MAX_PATH] of Char;
begin
if OpenDialog1.Execute then
begin
Label1.Caption := OpenDialog1.FileName;
Label1.Repaint;
StrPCopy(FilePath, OpenDialog1.FileName);
HIcon := ExtractAssociatedIcon(Application.Handle, @FilePath[0], iIcon);
if HIcon <> 0 then
Image1.Picture.Icon.Handle := HIcon;
end;
end;
2007. augusztus 26., vasárnap
How to check if a particular button on a TDBNavigator is enabled
Problem/Question/Abstract:
How to check if a particular button on a TDBNavigator is enabled
Answer:
type
TDBNavCracker = class(TDBnavigator);
{ ... }
if TDBNavCracker(DBNavigator1).Buttons[nbEdit].Enabled then
{ ... }
2007. augusztus 25., szombat
How to copy a record from one table to another
Problem/Question/Abstract:
Assuming I have Table1 and Table2, that have identical structures (same fields), how can I transfer all of the fields in the current record to a new record in the second table? For example, I want record no. 3 in Table1 to be appended to Table2.
Answer:
Solve 1:
for I := 0 to Table1.FieldCount do
Table2.Fields[I].AsVariant := Table1.Fields[I].AsVariant;
Solve 2:
Copies a record from a DataSet to a Table by field names. This is much safer than using the Fields property since the order of fields in the Fields array depends on the order of instantiation of the TField objects, not the order of fields in the table. This procedure assumes that the corresponding fields in the source and destination datasets have the same names.
Parameters:
Source = The source dataset
Destination = The destination table
procedure dgCopyRecordByName(Source: TDataSet; Destination: TTable);
var
LastField, L: Integer;
begin
Destination.Edit;
LastField := Source.FieldCount - 1;
for L := 0 to LastField do
begin
{Skip fields that do not exist in the destination table}
if Destination.FieldDefs.IndexOf(Source.FieldDefs[L].Name) < 0 then
Continue;
{Skip fields that are read only in the destination dataset}
if Destination.FieldByName(Source.FieldDefs[L].Name).ReadOnly then
Continue;
{Copy the field}
Destination.FieldByName(Source.FieldDefs[L].Name).Assign
(Source.FieldByName(Source.FieldDefs[L].Name));
end;
end;
Solve 3:
var
iCount: LongInt;
sName: string;
begin
Table2.Insert;
for iCount := 0 to Table2.FieldCount - 1 do
begin
sName := Table2.Fields[iCount].FieldName;
if (Table1.FindField(sName) <> nil) and (sName <> 'ID') then
Table2.FieldByName(sName).Assign(Table1.FieldByName(sName));
end;
Table2.Post;
end;
If you work with FieldByName, there are 2 advantages: You can copy only the fields you want. In the upper example, the field "ID" would not be copied. The construction of the two tables must not the same. Only fields with the same name would be copied. If you are sure, the construction is the same
for iCount := 0 to Table2.FieldCount - 1 do
Table2.Fields[iCount].Assign(Table1.Fields[iCount]);
also works.
Solve 4:
procedure CopyRecord(Tabelle: TTable);
var
feldwert: Variant;
i: Word;
begin
with DataModule1 do
begin
feldwert := VarArrayCreate([0, Tabelle.FieldCount - 1], varVariant);
for i := 0 to Tabelle.FieldCount - 1 do
feldwert[i] := Tabelle.Fields[i].Value;
Tabelle.Append;
for i := 0 to Tabelle.FieldCount - 1 do
Tabelle.Fields[i].Value := feldwert[i];
end;
end;
Solve 5:
procedure AppendCurrent(Dataset: TDataset);
var
aField: Variant;
i: Integer;
begin
{Create a variant Array}
aField := VarArrayCreate([0, DataSet.Fieldcount - 1], VarVariant);
{Read values into the array}
for i := 0 to (DataSet.Fieldcount - 1) do
begin
aField[i] := DataSet.fields[i].Value;
end;
DataSet.Append;
{Put array values into new the record}
for i := 0 to (DataSet.Fieldcount - 1) do
begin
DataSet.fields[i].Value := aField[i];
end;
end;
Solve 6:
The following is a chunk of code that I use to copy TTable rows. This function assumes that you are handling the insert and post calls yourself.
function CopyRow(Source, Dest: TTable): Boolean;
var
n: Integer;
begin
Result := False;
for n := 0 to Source.FieldCount - 1 do
begin
try
Dest.Fields[n].Assign(Source.Fields[n]);
except
Exit;
end;
end;
Result := True;
end;
2007. augusztus 24., péntek
How to get the text width in pixels when a component doesn't have a canvas
Problem/Question/Abstract:
How to get the text width in pixels when a component doesn't have a canvas
Answer:
If a component doesn't have a Canvas property you can use the following function to get the text width based on the font passed.
function GetTextWidth(CanvasOwner: TForm; Text: string; TextFont: TFont): Integer;
var
OldFont: TFont;
begin
OldFont := TFont.Create;
try
OldFont.Assign(CanvasOwner.Font);
CanvasOwner.Font.Assign(TextFont);
Result := CanvasOwner.Canvas.TextWidth(Text);
CanvasOwner.Font.Assign(OldFont);
finally
OldFont.Free;
end;
end;
2007. augusztus 23., csütörtök
Catching ALL mouse events
Problem/Question/Abstract:
I tried to override the MouseDown() method in a subclass of TForm to get every event for a general handler.
Answer:
The Application.OnMessage event will see all mouse messages before they are delivered to the control under the mouse. You work at the API level there, however. If none of the controls needs to do special mouse processing just hook the same event to all OnMouse* events of interest.
The Sender parameter will tell you which control fired the handler.
Start with the example below:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TForm1 = class(TForm)
ListBox1: TListBox;
procedure FormCreate(Sender: TObject);
private
{ private declarations }
procedure MyMouseEvent(var Msg: TMsg; var Handled: Boolean);
public
{ public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.MyMouseEvent(var Msg: TMsg; var Handled: Boolean);
var
s: string;
begin
case Msg.message of
wm_LButtonDown: s := 'left mouse down';
wm_LButtonUp: s := 'left mouse up';
wm_MouseMove: s := 'mouse move';
else
s := '';
end;
if s <> '' then
ListBox1.Items.Insert(0, s);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnMessage := MyMouseEvent;
end;
end.
2007. augusztus 22., szerda
Center the Windows "Browse for folder" directory picker on screen
Problem/Question/Abstract:
I am using a function from the ShlObj unit to select a networked computer via a Computer Browser. The browser window is called with SHBrowseForFolder(BrowseInfo), the window displayed always seems to position itself in the lower right of the screen. Is it possible to programatically reposition the window to be centered on the screen?
Answer:
Yes, you provide a browse callback function for this task.
uses
ActiveX, ShlObj;
function CenterVertical(const rect: TRect; h: Integer): Integer;
begin
Result := (rect.bottom + rect.top - h) div 2;
end;
function CenterHorizontal(const rect: TRect; w: Integer): Integer;
begin
Result := (rect.right + rect.left - w) div 2;
end;
function BrowserCallback(Wnd: HWND; uMsg: UINT; lParam, lpData: LPARAM): Integer
stdcall;
var
r1, r2: TRect;
begin
result := 0;
if uMsg = BFFM_INITIALIZED then
begin
GetWindowRect(wnd, r1);
r2 := Rect(0, 0, Screen.Width, Screen.Height);
MoveWindow(wnd, CenterHorizontal(r2, r1.Right - r1.left), CenterVertical(r2,
r1.Bottom - r1.Top),
r1.Right - r1.Left, r1.Bottom - r1.Top, false);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
browseinfo: TBrowseInfo;
pidl: PItemIDList;
buf: array[0..MAX_PATH] of Char;
begin
fillchar(browseinfo, SizeOf(browseinfo), 0);
browseinfo.hwndOwner := Handle;
browseinfo.lpszTitle := 'Select directory';
browseinfo.ulFlags := BIF_RETURNONLYFSDIRS or BIF_NEWDIALOGSTYLE;
browseinfo.lpfn := BrowserCallback;
pidl := ShBrowseForFolder(browseinfo);
if Assigned(pidl) then
begin
ShGetPathfromIDList(pidl, buf);
ShowMessage(buf);
CoTaskMemFree(pidl);
end;
end;
2007. augusztus 21., kedd
How to turn off the master volume of a sound card
Problem/Question/Abstract:
How to turn off the master volume of a sound card
Answer:
unit WaveUnit;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, MMSystem, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
Edit2: TEdit;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var
NumDevs: Integer;
waveCaps: TWaveOutCaps;
Volume: DWORD;
Left, Right: Word;
Version: Word;
begin
{ We should have at least one device }
NumDevs := waveOutGetNumDevs;
Edit1.Text := Format('Number of devices is %d', [NumDevs]);
{ for the 1st device (hard-coded) }
{Get Device Caps}
waveOutGetDevCaps(0, @waveCaps, SizeOf(waveCaps));
{ Show device caps }
Memo1.Lines.Add('Device Caps: ' + waveCaps.szPName);
Version := waveCaps.vDriverVersion;
Memo1.Lines.Add(Format('Driver Version: %d.%d', [Hi(Version), Lo(Version)]));
case waveCaps.wChannels of
1: Memo1.Lines.Add('Left');
2: Memo1.Lines.Add('Right');
end;
{ Standard formats }
if waveCaps.dwFormats and WAVE_FORMAT_1M08 <> 0 then
Memo1.Lines.Add('11.025 kHz, mono, 8-bit');
if waveCaps.dwFormats and WAVE_FORMAT_1M16 <> 0 then
Memo1.Lines.Add('11.025 kHz, mono, 16-bit');
{
WAVE_FORMAT_1S08 11.025 kHz, stereo, 8-bit
WAVE_FORMAT_1S16 11.025 kHz, stereo, 16-bit
WAVE_FORMAT_2M08 22.05 kHz, mono, 8-bit
WAVE_FORMAT_2M16 22.05 kHz, mono, 16-bit
WAVE_FORMAT_2S08 22.05 kHz, stereo, 8-bit
WAVE_FORMAT_2S16 22.05 kHz, stereo, 16-bit
WAVE_FORMAT_4M08 44.1 kHz, mono, 8-bit
WAVE_FORMAT_4M16 44.1 kHz, mono, 16-bit
WAVE_FORMAT_4S08 44.1 kHz, stereo, 8-bit
WAVE_FORMAT_4S16 44.1 kHz, stereo, 16-bit
}
{ If Volume Control Supported }
if waveCaps.dwSupport and WAVECAPS_VOLUME <> 0 then
begin
waveOutGetVolume(0, @Volume);
Left := LoWord(Volume);
Right := HiWord(Volume);
{ Show values of WAVE Device on volume control panel }
Edit2.Text := Format('Left : %d, Right : %d', [Left, Right]);
waveOutSetVolume(0, $40008000);
end;
end;
end.
2007. augusztus 20., hétfő
Standard RichEdit component and URL highlighting/navigation
Problem/Question/Abstract:
How can I highlight URLs in RichEdit and how can I detect a mouse click in text where URL is?
Answer:
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: Word;
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...
2007. augusztus 19., vasárnap
Memory leak in TCheckListBox
Problem/Question/Abstract:
I just found a memory leak in TCheckListBox (while using Delphi 3). Every time you check an item at runtime, a wrapper is created in routine TCheckListBox.GetWrapper in CheckLst.pas.
Answer:
These wrappers were supposed to be freed in procedure TCheckListBox.DestroyWnd; but this procedure is never called. Therefore all these pointers will never be freed.
The work around to this is to manually clear the listbox when the form is destroyed:
procedure TForm1.FormDestroy(Sender: TObject);
begin
CheckListBox1.Items.Clear;
inherited;
end;
2007. augusztus 18., szombat
Move a form with the mouse in the client area
Problem/Question/Abstract:
I once saw a very short example of moving a form with the mouse. It's the same like moving the form with the mouse in the caption of the form, but now it is moveable with the mouse in the client area. Has anyone an idea how to do this?
Answer:
In the private declarations of the form add this message handler:
private
{ Private declarations }
procedure WMNCHitTest(var M: TWMNCHitTest); message WM_NCHITTEST;
Then add this procedure
procedure TForm1.WMNCHitTest(var M: TWMNCHitTest);
begin
inherited;
if M.Result = htClient then
begin
{Client area is hit - but state it is the Title Bar}
if ((m.ypos - HelpWin.top) < HelpWin.height) then
M.Result := htCaption;
end;
end;
2007. augusztus 17., péntek
Hidden limitations in TIniFile
Problem/Question/Abstract:
I have an INI file that is approximately 20K with all entries in one section. If I use TIniFile's ReadSection method, only part of the section gets loaded. Why?
Answer:
A reader asked me this question a few days ago, and I must admit that it stumped me at first. He was trying to load an INI file's section that had several lines in it (amounting to over 16K of text) that he needed to load into a combo box. The section contained listings of several modem makes and models that he was going to use in his application so users could pick the modems that were on their machines.
To approach his problem, he created a TIniFile object and used the ReadSection method to read the section containing the list of modems into a TStrings object, which happened to be the Items property of a TComboBox. His code worked fine with one exception: ReadSection got about a third of the way through the list, then mysteriously stopped loading values, and truncated in the middle of a line! Intrigued, I decided to look into it, and much to my surprise, found a very interesting quirk in the code for ReadSection in the IniFiles.pas source file.
An Undocumented Limitation
The first stop in my investigation had me testing some code out in loading a huge section of an INI file into a ComboBox. I used the following procedure adapted from a snippet my reader sent to me:
procedure ComboLoadIniSection(IniFileName, SectionName: string; const List TStrings);
var
ini: TIniFile;
begin
list.clear;
if FileExists(IniFileName) then
ini := TIniFile.Create(IniFileName);
with ini do
try
ReadSection(SectionName, list);
finally
Free;
end;
end;
The code above looks pretty straightforward. In fact it works incredibly well, with absolutely no errors. I used it on some fairly generic INI files with just a few lines of key values first, and the Items property of my ComboBox was loaded just fine. It was when I used the sample file the reader sent containing the modem listings that things went awry. The procedure still executed fine with no errors, but truncated about a third of the way through the list. It looked like I was going to have to look into the source file.
Here's the listing for the ReadSection code in the IniFiles.Pas VCL Source file:
procedure TIniFile.ReadSection(const Section: string; Strings: TStrings);
const
BufSize = 8192;
var
Buffer, P: PChar;
begin
GetMem(Buffer, BufSize);
try
Strings.BeginUpdate;
try
Strings.Clear;
if GetPrivateProfileString(PChar(Section), nil, nil, Buffer, BufSize,
PChar(FFileName)) <> 0 then
begin
P := Buffer;
while P^ <> #0 do
begin
Strings.Add(P);
Inc(P, StrLen(P) + 1);
end;
end;
finally
Strings.EndUpdate;
end;
finally
FreeMem(Buffer, BufSize);
end;
end;
Looks like your basic WinAPI wrapper function. But there's one strange thing about it, and it has to do with the call to GetPrivateProfileString. This is a WinAPI-level call that is used to read a specific section of an INI file and loads one or all of its key values into a buffer. The buffer has the following structure: keyValue#0keyValue#0keyValue#0keyValue#0#0 where keyValue is a specific key value in a section. The WinAPI help file states that if the size of the strings in the section exceed the allocated buffer size, the buffer is truncated to the allocated size and two nulls are appended to the end of the string.
So going back to the code listing above, what do you see? Right! The buffer size is only 8K! So any section that has more than 8K in it will be truncated. That's why only part of the list was added into the ComboBox at runtime. I'm sure there was a good reason for the developer who wrote this wrapper to do this — probably to save memory space and go on the assumption that no one would ever need to have anything larger than 8K to read. But for those that do need to load in more than 8K, this is a serious limitation.
So how do you work around this? Well, at first thought, I figured upon creating a new descendant class off of TIniFile. But I checked myself because all the methods of TIniFile are static, so in order to do an override of a method, I'd have to write it over completely. Not a big deal, but then I'd have to deal with the overhead of adding the component into the VCL (and if you're like me, you've got a lot of components installed on your pallette). In the end, I decided to copy the source code and make a generic utility routine that I put in a library that I use for all my programs. Here's the code:
procedure INISectLoadList(IniFileName, SectionName: PChar; const list: TStrings);
const
BufSize = 32768; //Changed from 8192
var
Buffer, P: PChar;
begin
GetMem(Buffer, BufSize);
try
list.BeginUpdate;
try
list.Clear;
if GetPrivateProfileString(SectionName, nil, nil, Buffer, BufSize,
IniFileName) <> 0 then
begin
P := Buffer;
while P^ <> #0 do
begin
List.Add(P);
Inc(P, StrLen(P) + 1);
end;
end;
finally
List.EndUpdate;
end;
finally
FreeMem(Buffer, BufSize);
end;
end;
This is essentially a replica of the code above, with one exception: It now has a 32K buffer size. If you look up the GetPrivateProfileString in the help system, you'll see that the function is in the API code for backward compatibility with 16-bit applications. And as you may know, there is a 32K resource limit with 16- bit apps. Thus, your buffer can't be bigger than this. But this should be plenty of space to work with for 99 percent of the applications out there. However, for those of you making the move to Win95 and NT, the registry is where you should put runtime parameters.
Stay tuned for an article on the registry coming up. I'm still doing the research on it.
2007. augusztus 16., csütörtök
Copy one Excel worksheet to another
Problem/Question/Abstract:
Copy one Excel worksheet to another
Answer:
Call the copy method of that worksheet:
{ ... }
var
After: OleVariant;
Sh: _Worksheet;
begin
Sh := Excel.Worksheets['Sheet1'] as _Worksheet;
After := Excel.Workbooks[1].Sheets[3];
Sh.Copy(EmptyParam, After, lcid);
{ ... }
2007. augusztus 15., szerda
Fast sine and cosine calculations
Problem/Question/Abstract:
How to really speed up sine and cosine calculations
Answer:
If you have ever written applications that require many sine and cosine calculations over a short time you will have realized that things really start to slow down.
This is an old trick. But if you have never come across it, it really is worth using.
This version uses degrees not radians.
unit sin_Tool;
interface
const
FULL_CIRCLE = 360;
HALF_CIRCLE = 180;
// TEN_CIRCLES = 3600;
function MySin(x: integer): real; overload;
function MySin(x: real): real; overload; // allow both reals or integers
function MyCos(x: integer): real; overload;
function MyCos(x: real): real; overload; // allow both reals or integers
{ ===================================================== }
{ ===================================================== }
implementation
uses
Math;
const
MULTIPLIER = 10;
NUM_ELEMENTS = FULL_CIRCLE * MULTIPLIER;
type
tArcAnswers = array[0..NUM_ELEMENTS] of real;
var
SinResults,
CosResults: tArcAnswers;
{ =====================================================
function DegToRad(x:real):real; // OK... no need .. its in the math unit...
===================================================== }
procedure InitArcAnswers;
var
c: integer;
begin
for c := 0 to NUM_ELEMENTS do
begin
SinResults[c] := sin(DegToRad(c / MULTIPLIER));
CosResults[c] := cos(DegToRad(c / MULTIPLIER));
end;
c := 1;
end;
{ ===================================================== }
function MySin(x: integer): real; overload;
begin
while (x > FULL_CIRCLE) do
x := x - FULL_CIRCLE;
while (x < 0) do
x := x + FULL_CIRCLE;
Result := SinResults[x * MULTIPLIER];
end;
function MySin(x: real): real; overload;
begin
while (x > FULL_CIRCLE) do
x := x - FULL_CIRCLE;
while (x < 0) do
x := x + FULL_CIRCLE;
Result := SinResults[round(x * MULTIPLIER)];
end;
{ ===================================================== }
function MyCos(x: integer): real; overload;
begin
while (x > FULL_CIRCLE) do
x := x - FULL_CIRCLE;
while (x < 0) do
x := x + FULL_CIRCLE;
Result := CosResults[x * MULTIPLIER];
end;
function MyCos(x: real): real; overload;
begin
while (x > FULL_CIRCLE) do
x := x - FULL_CIRCLE;
while (x < 0) do
x := x + FULL_CIRCLE;
Result := CosResults[round(x * MULTIPLIER)];
end;
{ ===================================================== }
{ ===================================================== }
initialization
begin
InitArcAnswers;
end;
end.
Component Download: 3649.zip
2007. augusztus 14., kedd
How to get the printer port name
Problem/Question/Abstract:
How to get the printer port name
Answer:
Getting the printer port name:
{ ... }
uses
printers, winspool;
function GetCurrentPrinterHandle: THandle;
const
Defaults: TPrinterDefaults = (pDatatype: nil; pDevMode: nil; DesiredAccess:
PRINTER_ACCESS_USE or PRINTER_ACCESS_ADMINISTER);
var
Device, Driver, Port: array[0..255] of char;
hDeviceMode: THandle;
begin
Printer.GetPrinter(Device, Driver, Port, hDeviceMode);
if not OpenPrinter(@Device, Result, @Defaults) then
RaiseLastWin32Error;
end;
procedure TForm1.Button1Click(Sender: TObject);
procedure Display(const prefix: string; S: PChar);
begin
memo1.lines.add(prefix + string(S));
end;
var
pInfo: PPrinterInfo2;
bytesNeeded: DWORD;
hPrinter: THandle;
i: Integer;
begin
for i := 0 to printer.Printers.Count - 1 do
begin
Printer.PrinterIndex := i;
hPrinter := GetCurrentPrinterHandle;
try
GetPrinter(hPrinter, 2, nil, 0, @bytesNeeded);
pInfo := AllocMem(bytesNeeded);
try
GetPrinter(hPrinter, 2, pInfo, bytesNeeded, @bytesNeeded);
Display('ServerName: ', pInfo^.pServerName);
Display('PrinterName: ', pInfo^.pPrinterName);
Display('ShareName: ', pInfo^.pShareName);
Display('PortName: ', pInfo^.pPortName);
finally
FreeMem(pInfo);
end;
finally
ClosePrinter(hPrinter);
end;
end;
end;
2007. augusztus 13., hétfő
How to do a text search in a TMemoField
Problem/Question/Abstract:
How to do a text search in a TMemoField
Answer:
Here is an example of how to search a memo field. The field skills is a memo field:
procedure tdmdistbt.ANDFindSkills(SkillList, PeopleList: TStrings);
var
I, h, foundcount: Integer;
Skills: string;
begin
peoplelist.clear;
with tblTeamMember do
begin
first;
for I := 1 to recordcount do
begin
foundcount := 0;
Skills := uppercase(tblTeamMember.fieldbyname('Skills').asstring);
for h := 0 to skilllist.count - 1 do
begin
if Pos(uppercase(Skilllist[h]), Skills) > 0 then
inc(foundcount, 1);
end;
if foundcount = skilllist.count then
PeopleList.add(tblTeamMemberFullName.value);
next;
end;
end;
end;
2007. augusztus 12., vasárnap
Give Your Forms a Background
Problem/Question/Abstract:
Web pages use tiled bitmaps to create backgrounds. Is it possible to do this in Delphi?
Answer:
Before I learned how to do this, to create a background on a form, I'd drop a TImage on my form, then set its Align property to alClient. For low-resolution bitmaps, the pixelation that would occur at times was absolutely terrible! But with the method that I'll show you here (Note: this is merely ONE way of doing it), you can easily tile bitmaps on the surface of your form. The trick is in trapping the WM_ERASEBKGND message in a handler, creating a bitmap at runtime, then writing a quick bit of code in the OnPaint event handler. Let's go through the steps.
1. In the private section of your code place the following:
private
{ Private declarations }
MyBitmap: TBitmap;
procedure WMEraseBkgnd(var m: TWMEraseBkgnd);
�� message WM_ERASEBKGND;
Notice the declaration of MyBitmap. We'll be creating an instance for it below. The message handler for WM_ERASEBKGND looks like this:
procedure TBmpform.WMEraseBkgnd(var m: TWMEraseBkgnd);
begin
� m.Result := LRESULT(False);
end;
2. Then, create the following code for the OnPaint event handler Note: In the original article, the "x := x + MyBitmap.Width" is a bit inefficient in that continuously accessing the Bitmap.Width or .Height properties can slow things down - especially when you've got code in the OnPaint method. So what I did here was to simply set a couple of variables to store the Width and Height property values of the bitmap.
procedure TBmpForm.FormPaint(Sender: TObject);
var
x, y: Integer;
iBMWid, iBMHeight: Integer;
begin
iBMWid := MyBitmap.Width;
iBMHeight := MyBitmap.Height;
y := 0;
while y < Height do
begin
x := 0;
while x < Width do
begin
Canvas.Draw(x, y, MyBitmap);
x := x + iBMWid;
end;
y := y + iBMHeight;
end;
end;
3. Finally, create an instance of the bitmap you want to tile in the background in the OnCreate event of your form:
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnHint := ShowHint;
MyBitmap := TBitmap.Create;
MyBitmap.LoadFromFile('Brick4.bmp');
end;
4. Whoops, almost forgot! You need to destroy the bitmap when you exit!
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
bmpBackground.Free;
end;
Well, that's it. Don't you just love the quick and dirty ones?
2007. augusztus 11., szombat
Assign a password to a Paradox table
Problem/Question/Abstract:
Assign a password to a Paradox table
Answer:
To assign a password to a Paradox table, use the following unit and call function TablePasswort like this:
uses Unit2;
// ..
TablePasswort(Table1, 'secret');
unit Unit2;
interface
uses
BDE, SysUtils, DBTables, Windows;
function TablePasswort(var table: TTable; password: string): Boolean;
implementation
function StrToOem(const AnsiStr: string): string;
begin
SetLength(result, Length(AnsiStr));
if Length(result) > 0 then
CharToOem(PChar(AnsiStr), PChar(result))
end;
function TablePasswort(var table: ttable; password: string): Boolean;
var
pTblDesc: pCRTblDesc;
hDb: hDBIDb;
begin
result := false;
with table do
begin
if Active and (not Exclusive) then
Close;
if (not Exclusive) then
Exclusive := true;
if (not Active) then
Open;
hDb := DBHandle;
Close
end;
GetMem(pTblDesc, sizeof(CRTblDesc));
FillChar(pTblDesc^, sizeof(CRTblDesc), 0);
with pTblDesc^ do
begin
StrPCopy(szTblName, StrToOem(table.tablename));
szTblType := szParadox;
StrPCopy(szPassword, StrToOem(password));
bPack := true;
bProtected := true
end;
if DbiDoRestructure(hDb, 1, pTblDesc, nil, nil, nil, false) <> DBIERR_NONE then
exit;
if pTblDesc <> nil then
FreeMem(pTblDesc, sizeof(CRTblDesc));
result := true
end;
end.
2007. augusztus 10., péntek
Fake the caption bar of a borderless form
Problem/Question/Abstract:
How to fake the caption bar of a borderless form
Answer:
procedure TForm1.FormPaint(Sender: TObject);
var
r: TRect;
begin
r := Clientrect;
DrawEdge(canvas.handle, r, EDGE_RAISED, BF_RECT or BF_ADJUST);
r.bottom := r.top + GetSystemMetrics(SM_CYCAPTION);
DrawCaption(self.handle, canvas.handle, r, DC_ACTIVE or DC_ICON or DC_TEXT);
InflateRect(r, -2, -2);
r.Left := r.right - GetSystemMetrics(SM_CXSIZE) + 2;
DrawFrameControl(canvas.handle, r, DFC_CAPTION, DFCS_CAPTIONCLOSE);
r.right := r.left - 2;
r.Left := r.right - GetSystemMetrics(SM_CXSIZE) + 2;
DrawFrameControl(canvas.handle, r, DFC_CAPTION, DFCS_CAPTIONMAX);
r.right := r.left;
r.Left := r.right - GetSystemMetrics(SM_CXSIZE) + 2;
DrawFrameControl(canvas.handle, r, DFC_CAPTION, DFCS_CAPTIONMIN);
end;
2007. augusztus 9., csütörtök
How to get a zoomed preview of a full-size TScrollBox
Problem/Question/Abstract:
I have a TScrollBox. In it are between 10 to 300 other components (TCustomControls and TGraphic descendants) which are moveable and resizeable. For a better overview of the large scrollbox workspace I would like to write a small zoombox component showing an overview of the whole workspace in a small 50x50 pixel (or whatever size) area. Is there any easy Windows function for doing this fast? Or do I have to write an own routine?
Answer:
You have to write your own. Following is a little example that shows a 50% reduced preview of the full scrollbox. The controls edit1, edit2, shape1, shape2, image1, memo1 are all on the scrollbox, image2 is used for the preview, button1 triggers the painting of the preview. The main problem here is the way I use to paint a TWinControl owned by the scrollbox. The WM_PRINT message used is supported by all standard and common Windows controls, but not by pure VCL controls like TPanel or the grid classes. For those you may have to use WM_PAINT instead, or the PaintTo method.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ComCtrls, StdCtrls, ExtCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
ScrollBox1: TScrollBox;
Edit1: TEdit;
Edit2: TEdit;
Image1: TImage;
Shape1: TShape;
Shape2: TShape;
Memo1: TMemo;
Image2: TImage;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure PaintControl(aControl: TWinControl; aCanvas: TCanvas; offsetx, offsety: Integer);
begin
SaveDC(aCanvas.handle);
try
SetWindowOrgEx(aCanvas.handle, -(acontrol.left + offsetx), -(acontrol.top + offsety), nil);
acontrol.perform(WM_PRINT, acanvas.handle, PRF_CHILDREN or PRF_CLIENT or
PRF_NONCLIENT or PRF_ERASEBKGND);
finally
RestoreDC(aCanvas.handle, -1);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
bmp: TBitmap;
i: integer;
begin
bmp := TBitmap.Create;
try
bmp.width := scrollbox1.HorzScrollBar.Range div 2;
bmp.height := scrollbox1.VertScrollBar.Range div 2;
bmp.canvas.lock;
SetMapMode(bmp.canvas.handle, MM_ISOTROPIC);
SetWindowExtEx(bmp.canvas.handle, 200, 200, nil);
SetViewportExtEx(bmp.canvas.handle, 100, 100, nil);
try
SetWindowOrgEx(bmp.canvas.handle, -scrollbox1.HorzScrollBar.Position,
-scrollbox1.VertScrollBar.POsition, nil);
scrollbox1.perform(WM_PAINT, bmp.canvas.handle, 1);
SetWindowOrgEx(bmp.canvas.handle, 0, 0, nil);
for i := 0 to scrollbox1.controlcount - 1 do
if scrollbox1.controls[i] is TWincontrol then
Paintcontrol(TWincontrol(scrollbox1.Controls[i]), bmp.canvas,
scrollbox1.horzscrollBar.Position, scrollbox1.vertScrollBar.Position);
finally
bmp.canvas.unlock;
end;
image2.picture.bitmap := bmp;
finally
bmp.free;
end;
end;
end.
2007. augusztus 8., szerda
How to change the appearance of the focus rectangle in a TDBGrid
Problem/Question/Abstract:
I would like to be able to change the colour of the focus rectangle for certain cells and also to prevent it from being drawn for certain cells even when they have focus - is this possible? I have a graphic displayed in a column and I don't want it to be obscured by the blue focus rectangle - a transparent focus rectangle would be ok.
Answer:
procedure TForm1.DBGrid1DrawDataCell(Sender: TObject; const Rect: TRect;
Field: TField; State: TGridDrawState);
var
x: string;
begin
if Field.value = Null then
x := ''
else
x := FloatToStr(field.value);
with Sender as TDbGrid do
begin
if gdFocused in State then
begin
Canvas.Brush.Color := clYellow;
Canvas.Font.color := ClBlack;
end;
Canvas.FillRect(Rect);
Canvas.TextOut(Rect.Left + Canvas.Font.Size, Rect.Top + 2, x);
if gdFocused in State then
Canvas.DrawFocusRect(Rect);
end;
end;
2007. augusztus 7., kedd
Determine the type of an EXE file
Problem/Question/Abstract:
Determine the type of an EXE file
Answer:
Here's a function to return the platform the executable was designed for (16/32 bit Windows or DOS). Read the comment for the usage. This function works as well with DLLs, COMs, and maybe others. Thanks to Peter Below for this code.
Use it as shown at the bottom of the code snippet.
//-------------------------------------------------------------------------
// function to return the type of executable or dll (DOS, 16-bit, 32-bit).
//-------------------------------------------------------------------------
type
TExeType = (etUnknown, etDOS, etWinNE {16-bit}, etWinPE {32-bit});
function GetExeType(const FileName: string): TExeType;
var
Signature,
WinHdrOffset: Word;
fexe: TFileStream;
begin
Result := etUnknown;
try
fexe := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);
try
fexe.ReadBuffer(Signature, SizeOf(Signature));
if Signature = $5A4D { 'MZ' } then
begin
Result := etDOS;
fexe.Seek($18, soFromBeginning);
fexe.ReadBuffer(WinHdrOffset, SizeOf(WinHdrOffset));
if WinHdrOffset >= $40 then
begin
fexe.Seek($3C, soFromBeginning);
fexe.ReadBuffer(WinHdrOffset, SizeOf(WinHdrOffset));
fexe.Seek(WinHdrOffset, soFrombeginning);
fexe.ReadBuffer(Signature, SizeOf(Signature));
if Signature = $454E { 'NE' } then
Result := etWinNE
else if Signature = $4550 { 'PE' } then
Result := etWinPE;
end;
end;
finally
fexe.Free;
end;
except
end;
end;
begin
case GetExeType(aFileName) of
etUnknown: Label3.Caption := 'Unknown file type';
etDOS: Label3.Caption := 'DOS executable';
etWinNE: {16-bit} Label3.Caption := 'Windows 16-bit executable';
etWinPE: {32-bit} Label3.Caption := 'Windows 32-bit executable';
end;
end;
2007. augusztus 6., hétfő
How to create a TScrollBox with an own background
Problem/Question/Abstract:
How to create a TScrollBox with an own background
Answer:
unit NScroll;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms;
type
TMyScrollBox = class(TScrollBox)
private
FNHBitmap: TBitmap;
FNHCanvas: TCanvas;
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
procedure SetBitmap(Value: TBitmap);
protected
procedure Painting;
procedure PaintWindow(DC: HDC); override;
published
property BackBitmap: TBitmap read FNHBitmap write SetBitmap;
public
constructor Create(Owner: TComponent); override;
destructor Destroy; override;
end;
procedure Register;
implementation
constructor TMyScrollBox.Create(Owner: TComponent);
begin
inherited Create(Owner);
FNHBitmap := TBitmap.Create;
FNHCanvas := TControlCanvas.Create;
TControlCanvas(FNHCanvas).Control := Self;
end;
destructor TMyScrollBox.Destroy;
begin
FNHBitmap.Destroy;
FNHCanvas.Destroy;
inherited Destroy;
end;
procedure TMyScrollBox.SetBitmap(Value: TBitmap);
begin
FNHBitmap.Assign(Value);
invalidate;
end;
procedure TMyScrollBox.WMPaint(var Message: TWMPaint);
begin
PaintHandler(Message);
end;
procedure TMyScrollBox.PaintWindow(DC: HDC);
begin
FNHCanvas.Handle := DC;
try
Painting;
finally
FNHCanvas.Handle := 0;
end;
end;
procedure TMyScrollBox.Painting;
var
FDrawHeight, FDrawWidth: Integer;
Row, Column, xl, xt, xw, xh: Integer;
xdl, xdt: Integer;
xRect: TRect;
i: integer;
xhdl: Word;
begin
if (FNHBitmap.width <> 0) and (FNHBitmap.Height <> 0) then
begin
xRect := ClientRect;
FDrawHeight := xRect.Bottom - xRect.Top;
FDrawWidth := xRect.Right - xRect.Left;
xdl := (HorzScrollBar.Position mod FNHBitmap.Width);
xdt := (VertScrollBar.Position mod FNHBitmap.Height);
for Row := 0 to (FDrawHeight div FNHBitmap.Height) + 1 do
begin
for Column := 0 to (FDrawWidth div FNHBitmap.Width) + 1 do
begin
xl := Column * FNHBitmap.Width + xRect.Left - xdl;
xt := Row * FNHBitmap.Height + xRect.Top - xdt;
xw := FNHBitmap.Width;
if (FDrawWidth - xl + xRect.Left) < xw then
xw := (FDrawWidth - xl + xRect.Top);
xh := FNHBitmap.Height;
if (FDrawHeight - xt + xRect.Top) < xh then
xh := (FDrawHeight - xt + xRect.Top);
FNHCanvas.CopyRect(Rect(xl, xt, xl + xw, xt + xh), FNHBitmap.Canvas, Rect(0, 0, xw, xh));
end;
end;
end;
end;
procedure Register;
begin
RegisterComponents('Samples', [TMyScrollBox]);
end;
end.
2007. augusztus 5., vasárnap
How to store TForms and / or their properties in a Paradox blob field
Problem/Question/Abstract:
How to store TForms and / or their properties in a Paradox blob field
Answer:
procedure SaveToField(FField: TBlobField; Form: TComponent);
var
Stream: TBlobStream;
FormName: string;
begin
FormName := Copy(Form.ClassName, 2, 99);
Stream := TBlobStream.Create(FField, bmWrite);
try
Stream.WriteComponentRes(FormName, Form);
finally
Stream.Free;
end;
end;
procedure LoadFromField(FField: TBlobField; Form: TComponent);
var
Stream: TBlobStream;
I: integer;
begin
try
Stream := TBlobStream.Create(FField, bmRead);
try
{delete all components}
for I := Form.ComponentCount - 1 downto 0 do
Form.Components[I].Free;
Stream.ReadComponentRes(Form);
finally
Stream.Free;
end;
except
on EFOpenError do {nothing}
;
end;
end;
2007. augusztus 4., szombat
How to wallpaper the client area of a MDI parent form
Problem/Question/Abstract:
How to wallpaper the client area of a MDI parent form
Answer:
Solve 1:
Here are the basics of how it is done:
type
TForm1 = class(TForm)
Image1: TImage;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
FClientInstance,
FPrevClientProc: TFarProc;
procedure ClientWndProc(var Message: TMessage);
public
end;
implementation
procedure TForm1.ClientWndProc(var Message: TMessage);
var
MyDC: hDC;
Ro, Co: Word;
begin
with Message do
case Msg of
WM_ERASEBKGND:
begin
MyDC := TWMEraseBkGnd(Message).DC;
for Ro := 0 to ClientHeight div Image1.Picture.Height do
for Co := 0 to ClientWIDTH div Image1.Picture.Width do
BitBlt(MyDC, Co * Image1.Picture.Width, Ro * Image1.Picture.Height,
Image1.Picture.Width,
Image1.Picture.Height, Image1.Picture.Bitmap.Canvas.Handle, 0, 0,
SRCCOPY);
Result := 1;
end;
else
Result := CallWindowProc(FPrevClientProc, ClientHandle, Msg, wParam, lParam);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
if FileExists(ExtractFilePath(Application.ExeName) + 'backgrnd.bmp') then
begin
Image1.Picture.Bitmap.LoadFromFile(ExtractFilePath(Application.ExeName) +
'backgrnd.bmp');
FClientInstance := MakeObjectInstance(ClientWndProc);
FPrevClientProc := Pointer(GetWindowLong(ClientHandle, GWL_WNDPROC));
SetWindowLong(ClientHandle, GWL_WNDPROC, LongInt(FClientInstance));
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if (FPrevClientProc <> nil) then
begin
FClientInstance := Pointer(GetWindowLong(ClientHandle, GWL_WNDPROC));
SetWindowLong(ClientHandle, GWL_WNDPROC, Longint(FPrevClientProc));
FreeObjectInstance(FClientInstance);
end;
end;
Solve 2:
You need to do some Windows API level stuff to hook the window proc of MDI client window. This client window occupies the client area of an MDI main from - that's why you can't see the results of your painting.
Here's an example of how you do that. It also illustrates how to create a temporary canvas using a supplied Device Context to facilitate painting the image bitmap. The code looks for the file argyle.bmp in the Windows directory. If you don't have that bitmap, substitute another. Make sure you create an OnDestroy handler and copy the code from FormDestroy here into that handler.
{Example of painting the background of an MDI form}
unit MDIPaint;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs;
type
TForm1 = class(TForm)
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
FClientInstance: pointer;
FOldClientProc: pointer;
FBackground: TBitmap;
procedure ClientProc(var Message: TMessage);
public
{ Public declarations }
procedure CreateWnd; override;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.ClientProc(var Message: TMessage);
var
ARect: TRect;
x, y: integer;
SrcRect: TRect;
begin
{if the message is to erase background, tile with the background bitmap}
with Message do
begin
if Msg = WM_ERASEBKGND then
begin
WinProcs.GetClientRect(ClientHandle, ARect);
with TCanvas.Create do
try
Handle := wParam;
SrcRect := Rect(0, 0, FBackground.Width, FBackground.Height);
y := 0;
while y < ARect.Bottom do
begin
x := 0;
while x < ARect.Right do
begin
CopyRect(Bounds(x, y, FBackground.Width, FBackground.Height),
FBackground.Canvas, SrcRect);
inc(x, FBackground.Width);
end;
inc(y, FBackground.Height);
end;
Result := 1;
finally
Handle := 0;
Free;
end;
end
else
{otherwise call the original window proc}
Result := CallWindowProc(FOldClientProc, ClientHandle, Msg, wParam, lParam);
end;
end;
procedure TForm1.CreateWnd;
begin
inherited CreateWnd;
if FormStyle = fsMDIForm then
begin
FBackground := TBitmap.Create;
FBackground.LoadFromFile('c:\windows\argyle.bmp');
FClientInstance := MakeObjectInstance(ClientProc);
FOldClientProc := pointer(SetWindowLong(ClientHandle, GWL_WNDPROC,
longint(FClientInstance)));
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
{reset the original client proc, free the client instance and the bitmap}
SetWindowLong(ClientHandle, GWL_WNDPROC, longint(FOldClientProc));
FreeObjectInstance(FClientInstance);
FBackground.Free;
end;
end.
Solve 3:
Here are the steps to add a wallpaper to the client area of of a MDI parent form:
1. Create a new project
2. Set the form's FormStyle to fsMDIForm
3. Drop an image on the form and select a bitmap into it.
4. Find the { Private Declarations } comment in the form's definition and add these lines right after it:
FClientInstance, FPrevClientProc: TFarProc;
procedure ClientWndProc(var Message: TMessage);
5. Find the "implementation" line and the {$R *.DFM} line that follows it. After that line, enter this code:
procedure TForm1.ClientWndProc(var Message: TMessage);
var
MyDC: hDC;
Ro, Co: Word;
begin
with Message do
case Msg of
WM_ERASEBKGND:
begin
MyDC := TWMEraseBkGnd(Message).DC;
for Ro := 0 to ClientHeight div Image1.Picture.Height do
for Co := 0 to ClientWIDTH div Image1.Picture.Width do
BitBlt(MyDC, Co * Image1.Picture.Width, Ro * Image1.Picture.Height,
Image1.Picture.Width,
Image1.Picture.Height, Image1.Picture.Bitmap.Canvas.Handle, 0, 0,
SRCCOPY);
Result := 1;
end
else
Result := CallWindowProc(FPrevClientProc, ClientHandle, Msg, wParam, lParam);
end;
end;
6. Start an OnCreate method for the form and put these lines in it:
FClientInstance := MakeObjectInstance(ClientWndProc);
FPrevClientProc := Pointer(GetWindowLong(ClientHandle, GWL_WNDPROC));
SetWindowLong(ClientHandle, GWL_WNDPROC, LongInt(FClientInstance));
7. Add a new form to your project and set its FormStyle to fsMDIChild.
Now you have a working MDI project with "wallpaper". The image component is not visible, but its bitmap is replicated to cover the MDI form's client area. There is still one problem; when you minimize the child window its icon will be drawn against a gray rectangle.
2007. augusztus 3., péntek
How to copy text from a TRichEdit to the clipboard with a different font than the original
Problem/Question/Abstract:
I need to ensure that when my TRichEdit copies text to the clipboard, it is copied in a certain font, colour and size. My problem is that my TRichEdit is defaulted to one font and the users are not given the ability to change it. But I want it to pasted into Word (for example) in another font.
Answer:
You can of course compose a rich text file in code and copy that into the clipboard using the standard rich edit clipboard format, but it's a lot of work. A somewhat simpler approach may be to take the rich text as it is in the control (stream to a TMemoryStream, load into a String) and then modify the \fonttbl tag in the file.
procedure TForm1.Button3Click(Sender: TObject);
var
S: string;
ss: TStringstream;
ms: TMemoryStream;
begin
ms := TMemoryStream.Create;
try
richedit1.Lines.SaveToStream(ms);
SetString(S, Pchar(ms.Memory), ms.size);
finally
ms.free
end;
memo1.text := S; {view raw rtf in TMemo to see font table}
S := Stringreplace(S, 'Times New Roman', 'Verdana', []);
ss := TStringstream.Create(S);
try
richedit1.Lines.LoadFromStream(ss);
finally
ss.free
end;
end;
To get the new text into the clipboard proceed as below:
uses
Richedit, Clipbrd;
{$R *.dfm}
procedure CopyStreamToClipboard(fmt: Cardinal; S: TStream);
var
hMem: THandle;
pMem: Pointer;
begin
{Rewind stream position to start}
S.Position := 0;
{Allocate a global memory block the size of the stream data}
hMem := GlobalAlloc(GHND or GMEM_DDESHARE, S.Size);
if hMem <> 0 then
begin
{Succeeded, lock the memory handle to get a pointer to the memory}
pMem := GlobalLock(hMem);
if pMem <> nil then
begin
{Succeeded, now read the stream contents into the memory the pointer points at}
try
S.Read(pMem^, S.Size);
{Rewind stream again, caller may be confused if the stream position is
left at the end}
S.Position := 0;
finally
{Unlock the memory block}
GlobalUnlock(hMem);
end;
{Open clipboard and put the block into it. The way the Delphi clipboard
object is written this will clear the clipboard first.
Make sure the clipboard is closed even in case of an exception. If left open
it would become unusable for other apps.}
Clipboard.Open;
try
Clipboard.SetAsHandle(fmt, hMem);
finally
Clipboard.Close;
end;
end
else
begin
{Could not lock the memory block, so free it again and raise an out of
memory exception}
GlobalFree(hMem);
OutOfMemoryError;
end;
end
else
{Failed to allocate the memory block, raise exception}
OutOfMemoryError;
end;
var
CF_RTF: Word = 0; {set in Initialization section}
procedure TForm1.Button3Click(Sender: TObject);
var
S: string;
ss: TStringstream;
ms: TMemoryStream;
begin
ms := TMemoryStream.Create;
try
richedit1.Lines.SaveToStream(ms);
SetString(S, Pchar(ms.Memory), ms.size);
finally
ms.free
end;
S := Stringreplace(S, 'Times New Roman', 'Verdana', []);
ss := TStringstream.Create(S);
try
// richedit1.Lines.LoadFromStream(ss);
CopyStreamToClipboard(CF_RTF, ss);
finally
ss.free
end;
end;
initialization
CF_RTF := RegisterClipboardFormat(Richedit.CF_RTF);
end.
2007. augusztus 2., csütörtök
Retrieve all directories contained in a path string
Problem/Question/Abstract:
Suppose I have a path string, for example: 'c:\programs\borland\Delphi' , how can I retrieve each single directory name contained in the path (creating a tree of directories)?
Answer:
uses
SysUtils;
procedure GetDirList(const Root: AnsiString; Dirs: TStrings);
var
Found: TSearchRec;
function IsDirectory(F: TSearchRec): Boolean
begin
Result := F.Name <> '.';
Result := Result or (F.Name) <> '..';
Result := Result and (F.Attr and faDirectory = faDirectory);
end;
begin
Dirs.Clear;
if FindFirst(Root + '\*.*', faAnFile, Found) = 0 then
begin
try
if IsDirectory(Found) then
Dirs.Add(Root + '\' + Found.Name);
while FindNext(Found) = 0 do
begin
if IsDirectory(Found) then
Dirs.Add(Root + '\' + Found.Name);
end;
finally
FindFree
end;
end;
end;
2007. augusztus 1., szerda
Number in french plain text
Problem/Question/Abstract:
Converting integer to plain text in French
Answer:
function IntToLetters(N: Integer): string;
function Mille(N: Integer; P: Integer): string;
// Calcul des nombre de 0..99
function Cent(N: Integer): string;
const
X: array[0..20] of string =
('zero', 'un', 'deux', 'trois', 'quatre', 'cinq', 'six', 'sept', 'huit', 'neuf',
'dix',
'onze', 'douze', 'treize', 'quatorze', 'quinze', 'seize', 'dix-sept',
'dix-huit', 'dix-neuf', 'vingt');
Y: array[2..10] of string =
('vingt', 'trente', 'quarante', 'cinquante', 'soixante', 'soixante',
'quatre-vingt', 'quatre-vingt', 'cent');
var
A, B: Integer;
R, C: string;
begin
// Si le nombre est inferieur ou egal a 20 on a la solution directe
if (N <= 20) then
begin
R := X[N];
end;
// Si le nombre est superieur a 20
if (N > 20) and (N < 100) then
begin
// on prend la dizaine
A := N div 10;
// on pend l'unit�
B := N mod 10;
// si l'unit� est un, le s�parateur est 'et'
if (B = 1) and (A in [2, 3, 4, 5, 6, 7]) then
C := ' et '
else
C := ' ';
// si l'unite est sup�rieure a 1, le s�parateur est un '-'
if (B > 1) and (A in [2, 3, 4, 5, 6, 7, 8, 9]) then
C := '-';
// si la dizaine est 7 ou 9 on compte les unit�s de 10 ? 19
if (A = 7) or (A = 9) then
B := B + 10;
// On calcule la solution
if (B = 0) then
R := Y[A]
else
R := Y[A] + C + X[B];
end;
Result := R;
end;
// Calcul des nombres de 100..999
var
A, B: Integer;
R: string;
begin
if (N >= 100) then
begin
// on prend la centaine
A := N div 100;
// on prend le reste
B := N mod 100;
if (A = 0) or (A = 1) then
begin
// si la centaine est 0 ou 1
// on calcule et on 'cent' est au singulier
if (B = 0) then
R := 'cent '
else
R := 'cent ' + Cent(B);
end
else
begin
// si la centaine est > 1
if (P = 0) then
begin
// si c'est la fin d'un nombre (P=0)
// on mets 'cents' au pluriel si pas d'unit� sinon on met 'cent' au singulier
if (B = 0) then
R := Cent(A) + ' cents '
else
R := Cent(A) + ' cent ' + Cent(B);
end
else
begin
// si ce n'est pas la fin d'un nombre 'cent' est au singulier
if (B = 0) then
R := Cent(A) + ' cent '
else
R := Cent(A) + ' cent ' + Cent(B);
end;
end;
end
else
begin
// si le nombre est inf�rieur a 100 on le calcule directement
R := Cent(N);
end;
Result := R;
end;
// Function principale
const
Z: array[0..3] of string =
('', 'mille', 'million', 'milliard');
var
B, I: Integer;
R, M: string;
begin
R := '';
I := 0;
// On va d�composer en tranche de 1000 en partant de la droite
while (N > 0) do
begin
// prend une tranche (reste de la division par 1000)
B := N mod 1000;
// le pluriel est utilis� a partir des milliers
if (I = 0) then
M := ' '
else
M := 's ';
if I = 1 then
begin
// on calcule la tranche des milliers
// si le nombre de milliers est sup�rieur a 1 on ecrit le nombre et 'milles'
if (B > 1) then
R := Mille(B, I) + ' ' + Z[I] + M + R;
// sinon on �ecrit 'mille' et pas 'un mille'
if (B = 1) then
R := Z[I] + ' ' + R;
end
else
begin
// on calcule les millions et suivants
// on mets un 's' au pluriel
if (B > 1) then
R := Mille(B, I) + ' ' + Z[I] + M + R;
// on n'en met pas au singulier
if (B = 1) then
R := Mille(B, I) + ' ' + Z[I] + ' ' + R;
end;
// on decale de 3 rangs vers la droite
N := N div 1000;
I := I + 1;
end;
if (R = '') then
R := 'z�ro';
Result := R;
end;
Feliratkozás:
Bejegyzések (Atom)