2005. február 28., hétfő
How to create a random string
Problem/Question/Abstract:
How to create a random string
Answer:
Solve 1:
var
LoopInt: Integer;
DirName, FullName, RanStr: string;
FileSavedTo: TextFile;
{ Length of string to create }
RandArray: array[0..4087] of Char;
FirstCount: Extended;
begin
FirstCount := GetTickCount;
Label2.Caption := '';
Randomize;
RanStr := '';
DirName := Directory95ListBox1.Directory;
if DirName[Length(DirName)] <> '\' then
DirName := DirName + '\';
FullName := DirName + Edit1.Text;
if FileExists(FullName) then
DeleteFile(FullName);
for LoopInt := Low(RandArray) to High(RandArray) do
begin
RanStr := RanStr + Chr(Random(255 - 32 + 1) + 32);
end;
AssignFile(FileSavedTo, FullName);
if FileExists(FullName) then
Reset(FileSavedTo)
else
Rewrite(FileSavedTo);
Writeln(FileSavedTo, RanStr);
CloseFile(FileSavedTo);
Label2.Caption := ' Done ';
Label4.Caption := FloatToStr((GetTickCount - FirstCount) / 1000);
FileListBox1.Update;
end;
Randomize should be called only once in an application. You should therefore put the above code into the form's OnCreate event for example, or remove Randomize from the above code and call it from the form's OnCreate event handler.
Solve 2:
This routine creates passwords from a string table with selected chars. Note: The password length must be shorter than the given string table length.
{Call Randomize only once at application start.}
procedure TForm1.FormCreate(Sender: TObject);
begin
Randomize;
end;
function RandomPwd(PWLen: integer): string;
{Set the table of chars to be used in passwords}
const
StrTable: string = '!#$%&/()=?@<>|{[]}\*~+#;:.-_' + 'ABCDEFGHIJKLMabcdefghijklm' +
'0123456789' + '�������' + 'NOPQRSTUVWXYZnopqrstuvwxyz';
var
N, K, X, Y: integer;
begin
{Check the maximum password length}
if (PWlen > Length(StrTable)) then
K := Length(StrTable) - 1
else
K := PWLen;
SetLength(result, K); {Set the length of the result string}
Y := Length(StrTable); {Table length for inner loop}
N := 0; {Loop start value}
while N < K do
begin {Loop to create K chars}
X := Random(Y) + 1; {Get next random char}
{Check for the presence of this char in the result string}
if (pos(StrTable[X], result) = 0) then
begin
inc(N); {Not found }
Result[N] := StrTable[X];
end;
end;
end;
Used like this:
procedure TForm1.Button1Click(Sender: TObject);
var
cPwd: string;
begin
{e.g. create a random password string with 30 chars}
cPwd := RandomPwd(30);
{ ... }
end;
2005. február 27., vasárnap
How to display image transition effects on the table.next event
Problem/Question/Abstract:
How to display image transition effects on the table.next event
Answer:
Here's a rather simple example, using the :DBDEMOS:BIOLIFE table:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Grids, DBGrids, DB, DBTables, ExtCtrls;
type
TForm1 = class(TForm)
Table1: TTable;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
Image1: TImage;
Timer1: TTimer;
procedure DataSource1DataChange(Sender: TObject; Field: TField);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
Y: integer;
NewBitmap: TBitmap;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
const
BIOLifeWidth = 250;
BIOLifeHeight = 150;
procedure TForm1.DataSource1DataChange(Sender: TObject; Field: TField);
begin
if Table1.State = dsBrowse then
begin
if NewBitmap = nil then
begin
NewBitmap := TBitmap.Create;
Image1.Picture.Graphic := TBitmap.Create;
with TBitmap(Image1.Picture.Graphic) do
begin
Width := BIOLifeWidth;
Height := BIOLifeHeight;
end;
end;
NewBitmap.Assign(Table1.FieldByName('Graphic'));
Y := 0;
Timer1.Enabled := true;
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
R: TRect;
begin
R := Rect(0, Y, BIOLifeWidth, Y + 4);
with TBitmap(Image1.Picture.Graphic) do
begin
Canvas.CopyRect(R, NewBitmap.Canvas, R);
Inc(Y, 4);
if Y >= BIOLifeHeight then
Timer1.Enabled := false;
end;
end;
end.
2005. február 26., szombat
Select multiple rows in a TStringGrid
Problem/Question/Abstract:
Is there any way to allow the user to select multiple rows that are not consecutive for example selecting rows 1,4,5,13, and 16 but not 2,3,6,7,8... ?
Answer:
The standard selection mechanism build into the stringgrid only supports one consecutive block of selected cells (via the selection property). If you want more you have to code it. Find a way to store the selected state on a per-cell or per-row basis and then use the mouse events to give the user a means to change the state and a OnDrawCell handler to draw the cells accordingly.
I used the fixed column 0 in this grid to store the selected state for a row, the cell is either empty (not selected) or contains a space character (selected). I choose to draw a selection marker in column 0 instead of painting the selected rows with another background/ foreground color in this app. Something missing is the ability to click on the fixed column cell to toggle the selected state. I never got around to add that, you would use MouseToCell in OnMouseUp for that and make sure the Row property is set to that cells row before calling ToggleSelection. More work needs to be invested to support range selections as well.
const
sRowSelected = ' ';
sRowNotSelected = #0;
{This method is attached to the OnKeyPress event handler for the SGridIndications object. We use it to implement selection/ deselection of rows in the grid by a press of the spacebar. The grid is otherwise read-only.}
procedure TEinsendeMainForm.SGridIndicationsKeyPress(Sender: TObject; var Key: Char);
begin
if key = ' ' then
ToggleSelectedState;
key := #0;
end;
{This method is attached to the OnMouseUp event handler for the SGridIndications object. We use it to implement selection/deselection of rows in the grid by a click of the mouse. Any mouse button can be used.}
procedure TEinsendeMainForm.SGridIndicationsMouseUp(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
ToggleSelectedState;
end;
{procedure TEinsendeMainForm.SGridIndicationsDrawCell
Parameters:
Sender: the SGridIndications object
Col: column index for cell to draw
Row: row index for cell to draw
Rect: cell rectangle
State: cell state
Call method: static
Description:
This method is attached to the grids OnDrawCell event that is called every time a
cell needs to be draw. We rely mostly on the default drawing done by the grid
to display the data.The only thing we add is for the fixed column 0 cells:
if the cell contains a blank as a marker that it is selected we draw a red triangle as a visual cue for the selected state for this row. This type of selection is independend of the standard row selection the grid is set up for and allows any
number of rows to be selected and deselected individually. The grid does not support this kind of selection directly.
Error Conditions: none
Created: 29.06.97 11:58:58 by Peter Below
}
procedure TEinsendeMainForm.SGridIndicationsDrawCell(Sender: TObject;
Col, Row: Longint; Rect: TRect; State: TGridDrawState);
var
poly: array[1..3] of TPoint;
dy, dx: Integer;
begin
if (Col = 0) and ((Sender as TStringGrid).Cells[0, Row] = ' ') then
begin
with TStringGrid(Sender).Canvas do
begin
Brush.Color := clRed;
Pen.Color := clRed;
poly[1].X := Rect.Right - 5;
poly[1].Y := (Rect.Bottom - Rect.Top) div 2 + Rect.Top;
dy := (Rect.Bottom - Rect.Top) * 6 div 10;
dx := Round(Sqrt(3 * dy * dy) / 2);
poly[2].X := poly[1].X - dx;
poly[3].X := poly[2].X;
poly[2].Y := poly[1].Y - dy div 2;
poly[3].Y := poly[1].Y + dy div 2;
Polygon(poly);
end;
end;
end;
{function TEinsendeMainForm.CountSelectedIndications
Parameters: none
Returns:
the number of rows currently marked as selected in the indication grid.
Call method: static
Description:
Iterates over all rows of the grid and checks the content of column 0.
Error Conditions: none
Created: 29.06.97 13:42:31 by P. Below
}
function TEinsendeMainForm.CountSelectedIndications: Integer;
var
i: Integer;
begin
Result := 0;
with SGridIndications do
for i := 1 to RowCount - 1 do
if Cells[0, i][1] = sRowSelected then
Inc(Result);
end;
{procedure TEinsendeMainForm.UnselectAllIndications
|
Parameters: none
Call method: static
Description: Deselects all indications in the indication grid.
Error Conditions: none
Created: 07.07.97 13:28:28 by P. Below
}
procedure TEinsendeMainForm.UnselectAllIndications;
var
i: Integer;
begin
with sGridIndications do
for i := 1 to RowCount - 1 do
Cells[0, i] := sRowNotSelected;
end;
{procedure TEinsendeMainForm.ToggleSelectedState
Parameters: none
Call method: static
Description:
Inverts the selection state of the current row in the indication grid. Called by several event handlers for the grid.
Error Conditions: none
Created: 29.06.97 13:44:28 by P. Below
}
procedure TEinsendeMainForm.ToggleSelectedState;
begin
with SGridIndications do
if Cells[0, row] = sRowSelected then
Cells[0, row] := sRowNotSelected
else
Cells[0, row] := sRowSelected;
LblNumSelIndications.Caption := IntToStr(CountSelectedIndications) +
sIndicationsSelected;
end;
{procedure TEinsendeMainForm.SelectIndication
Parameters:
IndicationCode: the numeric (Prisma) code for the indication
state: new state (selected or unselected) to set.
Call method: static
Description: Searches thru the indication grid for the indication and sets it state, if found.
Error Conditions: none
Created: 07.07.97 13:31:41 by P. Below
}
procedure TEinsendeMainForm.SelectIndication(const IndicationCode: string; state:
Boolean);
var
i: Integer;
ch: Char;
begin
with sGridIndications do
begin
i := Cols[1].IndexOf(IndicationCode);
if i > 0 then
begin
if state then
ch := sRowSelected
else
ch := sRowNotSelected;
Cells[0, i] := ch;
end;
end;
end;
2005. február 25., péntek
Fix a small bug in TLabel.AutoSize
Problem/Question/Abstract:
Fix a small bug in TLabel.AutoSize
Answer:
If you switch between small fonts and large fonts, your labels' sizes will not be fixed. AutoSize works only when you change the label's caption or when you switch the property AutoSize on.
The following piece of code could be run e.g. in FormCreate to fix the sizes.
for I := 0 to ComponentCount - 1 do
if Components[I] is TLabel then
with TLabel(Components[I]) do
if AutoSize = True then
begin
AutoSize := False;
AutoSize := True;
end;
2005. február 24., csütörtök
How to draw checkboxes in a TDBGrid
Problem/Question/Abstract:
How to draw checkboxes in a TDBGrid
Answer:
Two procedures follow. The first is called from the OnDrawColumnCell event of any grid that has visible boolean fields, as so:
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
begin
DrawCheckBoxes(Sender, Rect, DataCol, Column, State);
end;
Here's the procedure. Place this in your toolkit or utility unit.
procedure DrawCheckBoxes(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
var
MyRect: TRect;
fld: TField;
begin
with (Sender as TDBGrid) do
begin
fld := Column.Field;
if fld is TBooleanField then
begin
MyRect.Top := ((Rect.Bottom - Rect.Top - 11) div 2) + Rect.Top;
MyRect.Left := ((Rect.Right - Rect.Left - 11) div 2) + Rect.Left;
MyRect.Bottom := MyRect.Top + 10;
MyRect.Right := MyRect.Left + 10;
if gdSelected in State then
Canvas.Pen.Color := clWhite
else
Canvas.Pen.Color := clBlack;
Canvas.Polyline([
Point(MyRect.Left, MyRect.Top), Point(MyRect.Right, MyRect.Top),
Point(MyRect.Right, MyRect.Bottom), Point(MyRect.Left, MyRect.Bottom),
Point(MyRect.Left, MyRect.Top)]);
if fld.AsBoolean then
begin
Canvas.MoveTo(MyRect.Left + 2, MyRect.Top + 4);
Canvas.LineTo(MyRect.Left + 2, MyRect.Top + 7);
Canvas.MoveTo(MyRect.Left + 3, MyRect.Top + 5);
Canvas.LineTo(MyRect.Left + 3, MyRect.Top + 8);
Canvas.MoveTo(MyRect.Left + 4, MyRect.Top + 6);
Canvas.LineTo(MyRect.Left + 4, MyRect.Top + 9);
Canvas.MoveTo(MyRect.Left + 5, MyRect.Top + 5);
Canvas.LineTo(MyRect.Left + 5, MyRect.Top + 8);
Canvas.MoveTo(MyRect.Left + 6, MyRect.Top + 4);
Canvas.LineTo(MyRect.Left + 6, MyRect.Top + 7);
Canvas.MoveTo(MyRect.Left + 7, MyRect.Top + 3);
Canvas.LineTo(MyRect.Left + 7, MyRect.Top + 6);
Canvas.MoveTo(MyRect.Left + 8, MyRect.Top + 2);
Canvas.LineTo(MyRect.Left + 8, MyRect.Top + 5);
end;
end;
end;
end;
There's a little setup involved. Select each visible boolean field in the fields editor and set the DisplayValues to ' ;'. That's space + semicolon. I like the DisplayWidth set to 2.
The next procedure is optional/ extra. It's a keystroke handler that will change the value of the field if the user presses space, T, F, Y, or N.Place it in your utility unit also and call it from your OnKeyPress event in the grid as so:
procedure TForm1.DBGrid1KeyPress(Sender: TObject; var Key: Char);
begin
CheckBoxKeyPress(Sender, Key);
end;
And here's the procedure:
procedure CheckBoxKeyPress(const Sender: TObject; var Key: Char);
var
fld: TField;
tbl: TDataset;
i: integer;
begin
if UpCase(Key) in [' ', 'T', 'F', 'Y', 'N'] then
begin
with (Sender as TDBGrid) do
begin
i := SelectedIndex;
fld := SelectedField;
tbl := fld.DataSet;
if fld is TBooleanField then
begin
if not (tbl.State in [dsEdit, dsInsert]) then
tbl.Edit;
if Key = ' ' then
fld.AsBoolean := not fld.AsBoolean
else if (UpCase(Key) = 'T') or (UpCase(Key) = 'Y') then
fld.AsBoolean := True
else
fld.AsBoolean := False;
tbl.Post;
Key := #0;
Inc(i);
if i = FieldCount then
begin
i := 0;
tbl.Next;
if tbl.EOF then
tbl.Append;
end;
SelectedIndex := i;
end;
end;
end;
end;
2005. február 23., szerda
How to display memo fields in a TDBGrid
Problem/Question/Abstract:
How to display memo fields in a TDBGrid
Answer:
procedure TForm1.DBGrid1DrawDataCell(Sender: TObject; const Rect: TRect;
Field: TField; State: TGridDrawState);
var
P: array[0..50] of char; {array size is number of characters needed}
BS: tBlobStream; {from the memo field}
S: string;
begin
if Field is TMemoField then
begin
with (Sender as TDBGrid).Canvas do
begin
BS := tBlobStream.Create(TBlobField(Field), bmRead);
FillChar(P, SizeOf(P), #0); {terminate the null string}
BS.Read(P, 50); {read 50 chars from memo into blobStream}
BS.Free;
S := StrPas(P);
while Pos(#13, S) > 0 do
S[Pos(#13, S)] := ' ';
while Pos(#10, S) > 0 do
S[Pos(#10, S)] := ' ';
FillRect(Rect); {clear the cell}
TextOut(Rect.Left, Rect.Top, S); {fill cell with memo data}
end;
end;
end;
For mouse right click behavior you need to intercept the right click mouse message.
2005. február 22., kedd
Check which column of a TListView in vsReport style has been clicked
Problem/Question/Abstract:
How can I know which column was click in a TListView? GetItemAt only works with the first column.
Answer:
Solve 1:
The method GetItemAt only provides the information about which ListItem (if any) is located at the specified coordinates passed as parameters, but only works with the first column of the TListView. The rest are ignored. If we needed to know if the user clicked on an element in another column, we can declare a new method in a derived class:
type
TListViewX = class(TListView)
public
function GetItemAtX(X, Y: integer; var Col: integer): TListItem;
end;
implementation
function TListViewX.GetItemAtX(X, Y: integer;
var Col: integer): TListItem;
var
i, n, RelativeX, ColStartX: Integer;
ListItem: TlistItem;
begin
Result := GetItemAt(X, Y);
if Result <> nil then
begin
Col := 0; // First column
end
else if (ViewStyle = vsReport)
and (TopItem <> nil) then
begin
// First, let's try to find the row
ListItem := GetItemAt(TopItem.Position.X, Y);
if ListItem <> nil then
begin
// Now let's try to find the Column
RelativeX := X - ListItem.Position.X - BorderWidth;
ColStartX := Columns[0].Width;
n := Columns.Count - 1;
for i := 1 to n do
begin
if RelativeX < ColStartX then
break;
if RelativeX <= ColStartX +
StringWidth(ListItem.SubItems[i - 1]) then
begin
Result := ListItem;
Col := i;
break;
end; //if
Inc(ColStartX, Columns[i].Width);
end; //for
end; //if
end; //if
end;
Casting to the new class
We don't need to intall this new component and register it in the components palette as we explained in another article ("Adding new methods and properties without registering new components"). Instead, any time we want to access this method, we can just cast the object (for example ListView1) to our new class. For example in a MouseDown event:
procedure TForm1.ListView1MouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
col: integer;
li: TListItem;
begin
li := TListViewX.GetItemAtX(x, y, col);
if li <> nil then
ShowMessage('Column #' + IntToStr(col));
end;
Solve 2:
uses
commctrl;
procedure TForm1.ListView1Click(Sender: TObject);
var
pt: TPoint;
col: Integer;
pos: Integer;
begin
GetCursorPos(pt);
pt := Listview1.ScreenToClient(pt);
Pos := -GetScrollPos(ListView1.Handle, SB_HORZ);
Col := -1;
while Pos < Pt.X do
begin
Inc(Col);
Inc(Pos, ListView_GetColumnWidth(ListView1.Handle, Col));
end;
if Col >= ListView1.Columns.Count then
Col := -1; {clicked past last column}
showmessage(inttostr(col));
end;
2005. február 21., hétfő
Application Settings (article 2)
Problem/Question/Abstract:
Managing Application Settings
Answer:
Introduction
In part 1 of this article, we looked at how we can automate the handling of application settings using a base object, TGXAppSettings. In part 2, we are going to look at using an object dataset to interface the application settings object to data aware controls.
The Object Dataset
Now that we have created our TRichEditSettings object, we need to somehow enable the end user of our application to manipulate the properties of TRichEditSettings. A first crack at doing this might look something like this:
procedure TForm1.LoadSettings;
begin
cbWordWrap.Checked := Settings.WordWrap;
edFontName.Text := Settings.FontName;
edFontSize.Text := IntToStr(Settings.FontSize);
end;
procedure TForm1.SaveSettings;
begin
Settings.WordWrap := cbWordWrap.Checked;
Settings.FontName := edFontName.Text;
Settings.FontSize := StrToInt(edFontSize.Text);
end;
This approach works fine when there is only a few properties to deal with, but in a real application there could be hundreds of properties and this approach will get very tedious, very quickly. Fortunately, due to architectural changes made in Delphi 3, we can do something about this.
In Delphi 1 and 2, data access was tightly bound to the BDE. Starting with Delphi 3, Borland abstracted the TDataset class thereby allowing anyone to create a provider of data. Since Delphi 3 was released lot of vendors have taken advantage of this by creating TDataset descendants that enable developers to access various datasources such as SQL Server, Interbase, Dbase without having to go through the BDE. I know what your asking yourself, how does this benefit us in this case?
Simple really. What we are going to do is create a TDataset descendant that treats objects as if they were a database. The object itself can be considered the "Table" while the properties will become the "Fields". By doing this we can directly connect the properties of our application settings object to data aware controls and thereby eliminate the work of transferring the information manually to visual controls and back again.
Using the Object Dataset
Now explaining how to write a complete dataset descendant is far beyond the scope of this article. Instead, I'm going to focus on how to use the object dataset I've written in the context of application settings.
The first thing we need to do is create an options form. Here a the picture of the one included in the sample code.
At the bottom of the form are three non-visual components. The leftmost component is the TRichEditSettings component, the middle component is the TGXObjectDataset and the right component is a standard TDataSource component.
Once we have dropped these components on the form, we need to hook them up. Set the GXObjectDataset's Component property to RichEditSettings1. You can create persistent field objects at this time if you desire, but generally there is no need to. In order to better show how the RichEditSettings properties become fields, I have included a picture below of the fields editor showing the persistent fields generated by the GXObjectDataset when connected to a TRichEditSettings component.
Next, connect the datasource to the GXObjectDataset. Finally, go through the data aware components and set the datasource and datafield property. Remember, every property of TRichEditSettings will appear as a field.
Now that this has been completed, we can write the code to show the options dialog from the main form. It's very straightforward.
procedure TMainForm.acOptionsExecute(Sender: TObject);
var
Dlg: TfmOptions;
begin
Dlg := TfmOptions.Create(Self);
try
Dlg.RichEditSettings1.Assign(RichEditSettings);
Dlg.GXObjectDataset1.Open;
if Dlg.ShowModal = mrOK then
begin
if (Dlg.GXObjectDataset1.State in dsEditModes) then
Dlg.GXObjectDataset1.Post;
RichEditSettings.Assign(Dlg.RichEditSettings1);
RichEditSettings.UpdateSettings(Editor);
SelectionChange(Editor);
end;
finally
Dlg.Free;
end;
end;
In the above code, we first create the options dialog. We then assign the current RichEditSettings object of the main form to the dialog's RichEditSettings. This means that the user is working on a copy. Next we open the GXObjectDataset on the dialog so that when the dialog is shown, the user will be able to edit the various values. After this, we then show the dialog modally.
If the user clicked the OK button, we check to see if the GXObjectDataset is in edit mode, and if so, post the changes. This will cause the underlying properties of the RichEditSettings component of the dialog to be updated with the user changes. We then assign the option dialog's RichEditSettings to the main forms RichEditSettings in order to capture the users changes. Finally, we update the richedit control with the new changes.
Conclusion
Well, this was a short Part 2 but I hope you have seen that managing application settings can be easy instead of being a chore.
Code
Download the code from this article here. Please be sure to read Install.txt included in the zip file before opening the project in Delphi.
2005. február 20., vasárnap
How to get the number of files in a folder
Problem/Question/Abstract:
How to get the number of files in a folder
Answer:
uses
Windows, { ... }
function FileCount(const aFolder: string): Integer;
var
H: THandle;
Data: TWin32FindData;
begin
Result := 0;
H := FindFirstFile(PCHAR(aFolder + '*.*'), Data);
if H <> INVALID_HANDLE_VALUE then
repeat
Inc(Result, Ord(Data.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0));
until
not FindNextFile(H, Data);
Windows.FindClose(H);
end;
2005. február 19., szombat
Get a name of enum value
Problem/Question/Abstract:
How can I retrieve the string name of some enumerate value?
Answer:
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;
2005. február 18., péntek
How to minimize a secondary-form to the taskbar
Problem/Question/Abstract:
How to minimize a secondary-form to the taskbar
Answer:
You can minimize a secondary-form to the taskbar using the following piece of code:
type
TForm = class(TForm)
...
private
{ Private declarations }
procedure CreateParams(var Params: TCreateParams); override;
end;
implementation
.....
procedure TForm.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
{ Set the extended style for iconizing to the taskbar }
{ See CreateWindowEx }
with Params do
exStyle := exStyle or WS_EX_APPWINDOW;
end;
2005. február 17., csütörtök
Sending e-mail with attachment using MS Outlook
Problem/Question/Abstract:
How to send e-mail with attachment using MS outlook
Answer:
Solve 1:
The unit that can do the job is scratched below:
unit OutLookMail;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Outlook8, OleServer, COMobj, ActiveX;
type
TMailRecord = record
FileToAttach: string;
MailTo: string;
CC: string;
BCC: string;
Subject: string;
Body: string;
end;
procedure OutLookMailProc(MailDetail: TMailRecord);
implementation
procedure OutLookMailProc(MailDetail: TMailRecord);
var
objOutlook: OutlookApplication;
CurrentInterface: IUnknown;
ActiveApplication: HResult;
CurrentMailItem: MailItem;
MailInspector: Inspector;
begin
ActiveApplication := GetActiveObject(CLASS_OutlookApplication, nil,
CurrentInterface);
if ActiveApplication = MK_E_UNAVAILABLE then
objOutlook := CoOutlookApplication.Create
else
begin
OleCheck(ActiveApplication);
OleCheck(CurrentInterface.QueryInterface(OutlookApplication, objOutlook));
end;
CurrentMailItem := objOutlook.CreateItem(0) as MailItem;
CurrentMailItem.To_ := MailDetail.MailTo;
if MailDetail.FileToAttach <> '' then
CurrentMailItem.Attachments.Add(MailDetail.FileToAttach, EmptyParam, EmptyParam,
EmptyParam);
CurrentMailItem.cc := MailDetail.CC;
CurrentMailItem.BCC := MailDetail.BCC;
CurrentMailItem.Subject := MailDetail.Subject;
CurrentMailItem.Body := MailDetail.Body;
MailInspector := CurrentMailItem.GetInspector;
MailInspector.Display(False);
Showmessage('I am waiting you to finish the mail process. Please click OK when done !');
objOutlook.Quit;
objOutlook := nil;
end;
end.
Unit for the Demo:
unit MailDemo;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Db, qrprntr, Qrctrls, qrExtra, qrexport, DBTables, QuickRpt, ExtCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
EditMailTo: TEdit;
Label1: TLabel;
Label2: TLabel;
EditSubject: TEdit;
Label3: TLabel;
EditFileToAttach: TEdit;
Memo1: TMemo;
Label4: TLabel;
Label5: TLabel;
EditCC: TEdit;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses OutLookMail;
{$R *.DFM}
const
CRLF = chr(13) + chr(10);
procedure TForm1.Button1Click(Sender: TObject);
var
MailDetail: TMailRecord;
x: integer;
begin
MailDetail.FileToAttach := EditFileToAttach.Text;
MailDetail.MailTo := EditMailTo.Text;
MailDetail.CC := EditCC.Text;
MailDetail.subject := EditSubject.Text;
MailDetail.Body := '';
for x := 0 to Memo1.Lines.Count - 1 do
MailDetail.Body := MailDetail.Body + Memo1.lines[x] + CRLF;
OutLookMailProc(MailDetail);
end;
end.
Component Download: MailDemo.zip
Solve 2:
procedure SendMail;
var
OleApp, OleItem: OleVariant;
begin
try
try
OleApp := GetActiveOleObject('Outlook.Application');
except
OleApp := CreateOleObject('Outlook.Application');
end;
OleItem := OleApp.CreateItem(0);
OleItem.Subject := 'Add Subject Here';
OleItem.Recipients.Add('Recipients Here');
OleItem.Attachments.Add('File Attachments Here');
OleItem.Body := 'EMail body text here';
OleItem.CC := 'Semi Colon delimited CC here';
OleItem.BCC := 'Semi Colon delimted BCC here';
OleItem.Send;
OleItem := VarNull;
OleApp := VarNull;
except
OleItem := VarNull;
OleApp := VarNull;
ShowMessage('EMail failed');
end;
end;
2005. február 16., szerda
Detect whether the default or the keypad Enter key was pressed
Problem/Question/Abstract:
How to detect whether the default or the keypad Enter key was pressed
Answer:
You could put something like this on an Application.OnMessage event, or trap the WM_KEYUP message in your component:
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnMessage := HandleMessage;
end;
procedure TForm1.HandleMessage(var Msg: TMsg; var Handled: Boolean);
begin
Handled := False;
if Msg.Message = WM_KEYUP then
begin
if Msg.wParam = VK_RETURN then
begin
if Msg.LParam and (1 shl 24) > 0 then
ListBox1.Items.Add('Keypad Return pressed')
else
ListBox1.Items.Add('Regular Return pressed')
end;
end;
end;
2005. február 15., kedd
Changing extension in Save Dialog
Problem/Question/Abstract:
How can I make the Save dialog automatically change the file extension of the filename when the user selects a different Filter?
Answer:
The solution to this problem is to handle the OnTypeChange event of the TSaveDialog and directly send a message to the dialog. Start by assigning an event handler for the OnTypeChange event (this is called whenever the user selects a different filter in the dialog). Delphi keeps track of which FilterIndex is currently selected even when the dialog is open, so we can write the following code:
procedure TForm1.SaveDialogTypeChange(Sender: TObject);
var
buf: array[0..MAX_PATH] of char;
S: string;
od: TSaveDialog;
H: THandle;
begin
// get a pointer to the dialog
od := (Sender as TSaveDialog);
// Send the message to the dialogs parent so it can handle it the normal way
H := GetParent(od.Handle);
// get the currently entered filename
SendMessage(H, CDM_GETSPEC, MAX_PATH, integer(@buf));
S := buf;
// change the extension to the correct one
case od.FilterIndex of
1:
S := ChangeFileExt(S, '.rtf');
2:
S := ChangeFileExt(S, '.html');
3:
S := ChangeFileExt(S, '.txt');
end;
// finally, change the currently selected filename in the dialog
SendMessage(H, CDM_SETCONTROLTEXT, edt1, integer(PChar(S)));
end;
In the example, I have three filters for RTF, HTML and TXT and the code changes the extension to the correct one simply by calling ChangeFileExt on the existing filename. The CDM_* constants are defined in CommDlg.pas, so you must add this to your uses clause (or redeclare them in your unit). The constant edt1 is taken from the file Dlgs.pas where every constant used in the common dialogs are listed. edt1 is the first edit control on any common dialog, edt2 the second etc.
2005. február 14., hétfő
Searching Strings by the way they sound
Problem/Question/Abstract:
Did you ever want to find a string - But were not sure of it's spelling? A typical case would be names (Micael/Maical/Michael/Maichael) all sound same but differ in spelling!
Answer:
Most of you may already be familiar with the magical "Soundex" function which is present in many Db environments ranging from FoxPro to Oracle/SQL Server. Few of you may wonder how it works! Well, here is the implementation of the Soundex function in Pascal based on an algorithm that I found in a computer magazine long time back. The original program worked in Turbo Pascal, but I have modified it for Delphi (The only change being use of ShortString instead of String!)
The function seems to return the same values as does SQL Server for the little tests that I conducted. However, as you will have already guessed, I provide you no gurantee that it will provide same values for all strings.
Please save the code below in a file called Soundx.pas. You will need to include the file in your source (Uses Soundx) and then you will have access to the Soundex() function.
For the example given in the Question/Problem/Abstract, Soundex returns the same value (M240) for each of Micael/Maical/Michael/Maichael
Wishing you all a "Sound" search (Ha!)
{******************************************************}
{* Description: Implementation of Soundex function *}
{******************************************************}
{* Last Modified : 12-Nov-2000 *}
{* Author : Paramjeet Singh Reen *}
{* eMail : Paramjeet.Reen@EudoraMail.com *}
{******************************************************}
{* This program is based on the algorithm that I had *}
{* found in a magazine. I do not gurantee the fitness *}
{* of this program. Please use it at your own risk. *}
{******************************************************}
{* Category :Freeware. *}
{******************************************************}
unit Soundx;
interface
type
SoundexStr = string[4];
//Returns the Soundex code for the specified string.
function Soundex(const InpStr: ShortString): SoundexStr;
implementation
const
Alphs: array['A'..'Z'] of Char = ('0', '1', '2', '3', '0', '1', '2', '0', '0', '2',
'2',
'4', '5', '5', '0', '1', '2', '6', '2', '3', '0', '1',
'0', '2', '0', '2');
function Soundex(const InpStr: ShortString): SoundexStr;
var
vStr: ShortString;
vCh1: Char;
i: Word;
begin
//Store the given InpStr in local variable in uppercase
vStr := '';
for i := 1 to Length(InpStr) do
vStr := vStr + UpCase(InpStr[i]);
//Replace all occurances of "PH" with "F"
i := Pos('PH', vStr);
while (i > 0) do
begin
Delete(vStr, i, 2);
Insert('F', vStr, i);
i := Pos('PH', vStr);
end;
//Replace all occurances of "CHR" with "CR"
i := Pos('CHR', vStr);
while (i > 0) do
begin
Delete(vStr, i, 3);
Insert('CR', vStr, i);
i := Pos('CHR', vStr);
end;
//Replace all occurances of "Z" with "S"
for i := 1 to Length(vStr) do
if (vStr[i] = 'Z') then
vStr[i] := 'S';
//Replace all occurances of "X" with "KS"
i := Pos('X', vStr);
while (i > 0) do
begin
Delete(vStr, i, 1);
Insert('KS', vStr, i);
i := Pos('X', vStr);
end;
//Remove all adjacent duplicates
i := 2;
while (i <= Length(vStr)) do
if (vStr[i] = vStr[i - 1]) then
Delete(vStr, i, 1)
else
Inc(i);
//Starting from 2nd char, remove all chars mapped to '0' in Alphs table
i := 2;
while (i <= Length(vStr)) do
if (Alphs[vStr[i]] = '0') then
Delete(vStr, i, 1)
else
Inc(i);
//Assemble Soundex string from Alphs table
vCh1 := vStr[1];
for i := 1 to Length(vStr) do
vStr[i] := Alphs[vStr[i]];
//Remove all adjacent duplicates from assembled Soundex string
i := 2;
while (i <= Length(vStr)) do
if (vStr[i] = vStr[i - 1]) then
Delete(vStr, i, 1)
else
Inc(i);
//Final assembly of Soundex string
vStr := vCh1 + Copy(vStr, 2, 255);
for i := Length(vStr) to 3 do
vStr := vStr + '0';
Soundex := vStr;
end;
end.
2005. február 13., vasárnap
Retrieve a folder list from MS Outlook
Problem/Question/Abstract:
Do you want to use the MS Outlook from Delphi application?
Answer:
The procedure below allow to load a tree of available folders into TTreeView:
procedure 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:
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
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
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.
2005. február 12., szombat
How to embed binary data in an executable
Problem/Question/Abstract:
I have a very specialized script language which requires an executable program (The Engine) to load and process scripts. This works on the basis that each script requires its own copy of the executable processing engine (for reasons about to be explained). I want to take this a step further by embedding a script inside the executable at runtime. This will be done from my existing script editor. Something like a compile script function. How can I open an executable file, safely add a block of binary data which can then be read when the executable is running? I know this can be done. If virus writers can put additional executable code into an *.exe file, then I must be able to put binary data in.
Answer:
I wrote this component to embed data in forms or datamodules. Drop the component in the form, double click it and select the file to embed. I have written a version that compresses the data also, but I lost it, anyway, is not complicated to do so if you want compressed data.
unit uBinaryData;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, DsgnIntf;
type
TBinaryData = class(TComponent)
private
MemStream: TStream;
TempFileName: string;
procedure WriteData(Stream: TStream);
procedure ReadData(Stream: TStream);
procedure SetStream(Stream: TStream);
function GetDataSize: Longint;
procedure SetDataSize(ASize: Longint);
protected
procedure DefineProperties(Filer: TFiler); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function GetTempFile(const Ext: string): string;
procedure DeleteTempFile;
procedure SaveToFile(const FName: string);
property Stream: TStream read MemStream write SetStream;
published
property DataSize: Longint read GetDataSize write SetDataSize;
end;
TBinaryDataEditor = class(TComponentEditor)
protected
function GetVerbCount: Integer; override;
function GetVerb(index: Integer): string; override;
procedure ExecuteVerb(index: Integer); override;
procedure Edit; override;
end;
procedure Register;
implementation
{ TBinaryData }
constructor TBinaryData.Create;
begin
inherited;
MemStream := TMemoryStream.Create;
end;
destructor TBinaryData.Destroy;
begin
MemStream.Free;
if TempFileName <> '' then
DeleteTempFile;
inherited;
end;
function TBinaryData.GetDataSize;
begin
Result := MemStream.Size;
end;
procedure TBinaryData.SetDataSize;
begin
(MemStream as TMemoryStream).SetSize(ASize);
end;
procedure TBinaryData.DefineProperties;
begin
inherited;
Filer.DefineBinaryProperty('TheData', ReadData, WriteData, true);
end;
procedure TBinaryData.ReadData;
var
ASize: Longint;
begin
Stream.Read(ASize, sizeof(ASize));
if ASize > 0 then
begin
(MemStream as TMemoryStream).SetSize(ASize);
Stream.Read((MemStream as TMemoryStream).Memory^, ASize);
end;
end;
procedure TBinaryData.WriteData;
var
ASize: Longint;
begin
ASize := MemStream.Size;
Stream.Write(ASize, sizeof(ASize));
if ASize > 0 then
Stream.Write((MemStream as TMemoryStream).Memory^, ASize);
end;
procedure TBinaryData.SetStream;
begin
if Stream <> nil then
(MemStream as TMemoryStream).LoadFromStream(Stream)
else
(MemStream as TMemoryStream).SetSize(0);
end;
function TBinaryData.GetTempFile;
const
FirstChars: PChar = 'AAA';
var
PathBuffer: array[0..255] of char;
FileName: array[0..MAX_PATH] of char;
FileStream: TFileStream;
begin
GetTempPath(256, PathBuffer);
if GetTempFileName(PathBuffer, FirstChars, 0, FileName) = 0 then
raise Exception.Create('No se pudo crear el archivo temporal');
Result := StrPas(FileName);
DeleteFile(Result);
Result := ChangeFileExt(Result, Ext);
TempFileName := Result;
FileStream := TFileStream.Create(Result, fmCreate);
try
MemStream.Seek(0, 0);
FileStream.CopyFrom(MemStream, MemStream.Size);
finally
FileStream.Free;
end;
end;
procedure TBinaryData.DeleteTempFile;
begin
DeleteFile(TempFileName);
TempFileName := '';
end;
procedure TBinaryData.SaveToFile;
var
s: TFileStream;
begin
s := TFileStream.Create(FName, fmCreate);
try
Stream.Seek(0, 0);
s.CopyFrom(Stream, Stream.Size);
finally
s.Free;
end;
end;
{ TBinaryDataEditor }
function TBinaryDataEditor.GetVerbCount;
begin
Result := 1;
end;
function TBinaryDataEditor.GetVerb;
begin
Result := 'Load File...';
end;
procedure TBinaryDataEditor.ExecuteVerb;
begin
Edit;
end;
procedure TBinaryDataEditor.Edit;
var
OpenDialog: TOpenDialog;
FileStream: TFileStream;
begin
OpenDialog := TOpenDialog.Create(Application);
try
OpenDialog.Filter := '*.*';
if OpenDialog.Execute then
if FileExists(OpenDialog.Filename) then
begin
FileStream := TFileStream.Create(OpenDialog.Filename, fmOpenRead);
try
(Component as TBinaryData).Stream := FileStream;
Designer.Modified;
finally
FileStream.Free;
end;
end;
finally
OpenDialog.Free;
end;
end;
procedure Register;
begin
RegisterComponents('Misc', [TBinaryData]);
RegisterComponentEditor(TBinaryData, TBinaryDataEditor);
end;
end.
2005. február 11., péntek
How to resize controls according to the users font settings
Problem/Question/Abstract:
How to resize controls according to the users font settings
Answer:
You don't need the resolution (unless you plan on rearranging the controls on your form depending on resolution). You can get the appropriate "base unit" for sizing controls like this:
BaseUnit := Canvas.TextHeight('0');
This returns a value which is 8 times what Windows calls a "dialog unit." Every control can be sized to some integral number of dialog units. For example, I normally make my buttons 40 units wide and 14 units tall. So the code would go something like this:
ButtonWidth := (40 * BaseUnit) div 8;
ButtonHeight := (14 * BaseUnit) div 8;
MyButton.SetBounds(L, T, ButtonWidth, ButtonHeight);
This will resize the button in accordance with whatever the user's font size setting happens to be. It is the font.height that changes during the automatic scaling of the form (if you leave form.scaled set to true) and this will make the component scale as well.
2005. február 10., csütörtök
Make my program open a file specified as a command line parameter?
Problem/Question/Abstract:
How do I make my program open a file specified as a command line parameter?
Answer:
To do this you need to use two functions - ParamCount and ParamStr. ParamCount returns the number of command line parameters specified when the program was run. ParamStr returns the parameter string of a specified parameter.
Basically all you need to do is check to see whether any parameters have been passed, and if so evaluate them. The format of the parameter(s) is entirely up to you, and you can produce code to deal with anything from a single parameter to a whole range.
This simple example only allows for a single parameter - a file name - and if a file name is passed the program loads that file when the form is shown. It requires a single form with a Memo dropped onto it. Simply put the following code into the form's OnShow event:
procedure TForm1.FormShow(Sender: TObject);
begin
��Memo1.Clear;
��if ParamCount > 0 then
��begin
�� ��case ParamCount of
�� ����1: Memo1.Lines.LoadFromFile(Paramstr(1));
�� ����// allow for other possible parameter counts here
�� ��else
�� �����begin
�� �������ShowMessage('Invalid Parameters');
�� �������Application.Terminate;
�� �����end;
�� end;
end;
To prove this code, after compiling the program of course, select Start | Run and enter the following. Make sure that you replace the path of the exe file with the correct path for your machine:
"F:\Borland\Delphi 3\Project1.exe" "c:\windows\win.ini"
This will open the Win.ini file in the memo in the application you created. Obviously this example could be extended considerably (there is no check to make sure that the file exists, for example) and the parameters could be parsed to determine what should be done with the information. It does not have to be a file opening command, it could just as easily be configuration information or indeed anything else that you may wish to specify when the program is run.
2005. február 9., szerda
How to save a metafile displayed in a TImage as a bitmap
Problem/Question/Abstract:
How to save a metafile displayed in a TImage as a bitmap
Answer:
Try to give the width and height of the image in the bitmap before drawing the metafile or use the assign method of the bitmap to get the results you want. For example:
{ ... }
var
aBitmap: TBitmap;
begin
aBitmap := TBitmap.Create;
aBitmap.width := Image.picture.width;
aBitmap.height := Image1.picture.height;
try
aBitmap.Canvas.Draw(0, 0, Image1.Picture.Metafile);
abitmap.SaveToFile('D:\temp\mybit.bmp');
finally
aBitmap.free;
end;
or try:
{ ... }
var
aBitmap: TBitmap;
begin
aBitmap := TBitmap.Create;
try
aBitmap.assign(image1.picture.graphic);
aBitmap.SaveToFile('D:\temp\mybit.bmp');
finally
aBitmap.free;
end;
2005. február 8., kedd
Pitfalls reading from the Registry (ProxyEnable)
Problem/Question/Abstract:
Pitfalls reading from the Registry (ProxyEnable)
Answer:
The following has been verified for Delphi 3. It may be slightly different for Delphi 5.
In a project I had to read the Boolean field “ProxyEnable” from the registry. It is stored in
HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings
On the systems that I work with (NT 4.0/ sp 5, NT 4.0/ sp 6, Win 2000/ sp 1) I checked and determined that this value was stored as a REG_DWORD. 0 meant false, 1 meant true. So I simply coded as shown in 1)
Pitfall 1): Unexpected Entry Type
On some customer machines, the value ProxyEnable was stored as a REG_BINARY. Reading a REG_BINARY with ReadInteger() causes an exception – and to make things worse, exception handling was not yet in place since it was executed during the initialization of my application.
So I changed my code to 2) and hoped it would work.
Pitfall 2): Read the exact length
This was a surprising one. When you have a registry value of type REG_BINARY that is 4 bytes long and you try reading it with
ReadBinaryData("MyKey", myBooleanVar, 1);
… then you will experience another exception. The correct code is as shown in part 3.
// Version 1:
// raises an exception because of the unexpected entry type
ProxyEnabled := ReadBool(sProxyEnable);
// Version 2:
// detect type with GetDataType(),
// but not size... raises an exception if the size is wrong
case GetDataType(sProxyEnable) of
rdInteger:
ProxyEnabled := ReadBool(sProxyEnable);
rdBinary:
ReadBinaryData(sProxyEnable, ProxyEnabled, 1);
// other types..
end;
// Version 3:
// detect type and size (GetDataSize())! Works so far.
case GetDataType(sProxyEnable) of
rdInteger:
ProxyEnabled := ReadBool(sProxyEnable);
rdBinary:
ReadBinaryData(sProxyEnable, ProxyEnabled, GetDataSize(sProxyEnable));
// other types..
end;
2005. február 7., hétfő
How to get a list of all published methods for a given class
Problem/Question/Abstract:
Is there a way to get a list of all published methods for a given class? Preferably, I'd like to fill a TList with references to them, but even the text representation would work.
Answer:
Enumerate the published methods of a class and all its ancestor classes:
procedure EnumMethods(aClass: TClass; lines: TStrings);
type
TMethodtableEntry = packed record
len: Word;
adr: Pointer;
name: ShortString;
end;
{Note: name occupies only the size required, so it is not a true shortstring!
The actual entry size is variable, so the method table is not an array
of TMethodTableEntry!}
var
pp: ^Pointer;
pMethodTable: Pointer;
pMethodEntry: ^TMethodTableEntry;
i, numEntries: Word;
begin
if aClass = nil then
Exit;
pp := Pointer(Integer(aClass) + vmtMethodtable);
pMethodTable := pp^;
lines.Add(format('Class %s: method table at %p', [aClass.Classname, pMethodTable]));
if pMethodtable <> nil then
begin
{first word of the method table contains the number of entries}
numEntries := PWord(pMethodTable)^;
lines.Add(format(' %d published methods', [numEntries]));
{make pointer to first method entry, it starts at the second word of the table}
pMethodEntry := Pointer(Integer(pMethodTable) + 2);
for i := 1 to numEntries do
begin
with pMethodEntry^ do
lines.Add(format(' %d: len: %d, adr: %p, name: %s', [i, len, adr, name]));
{make pointer to next method entry}
pMethodEntry := Pointer(Integer(pMethodEntry) + pMethodEntry^.len);
end;
end;
EnumMethods(aClass.ClassParent, lines);
end;
procedure TForm2.Button1Click(Sender: TObject);
begin
memo1.clear;
EnumMethods(Classtype, memo1.lines);
end;
2005. február 6., vasárnap
How to compare the contents of folders including subdirectories
Problem/Question/Abstract:
I need to compare the file contents of a folder and its subfolders on all Win98 machines at my client's site (each machine was setup differently but, over time, has had different patches applied to the program in question). I need to report back only the differences found in files based upon Time/ Date stamp. Is there a good way to do this?
Answer:
You use a recursive scanning loop (FindFirst/ FindNext/ FindClose) starting at the topmost folder you need to examine. The loop stores each file it find into a TStringlist. It stores a relative path to the start folder. For each file found it also stores the searchrec.time into the Objects property of the stringlist (it uses AddObject instead of Add). It will fit with a little typecast since it is four bytes, like an object reference. After the end of the scan you have a list of all files, which you can now write to disk for your reference computer to produce a master list that will be used on the other PCs to find the differences. The output file needs to contain the timestamp, of course, so it would be produced with something like:
procedure SaveScan(files: TStringlist; const filename: string);
var
f: textfile;
i: Integer;
begin
assignfile(f, filename);
rewrite(f);
try
for i = 0 to files.count - 1 do
writeLn(f, Format('%p%s', [pointer(files.Objects[i]), files[i]]));
finally
closefile(f);
end;
end;
Reading the list back would be:
procedure LoadScan(files: TStringlist; const filename: string);
var
f: textfile;
S: string;
begin
assignfile(f, filename);
reset(f);
try
files.clear;
while not EOF(f) do
begin
ReadLn(f, S);
files.AddObject(Copy(S, 9, Maxint), TObject(StrToInt('$' + Copy(S, 1, 8))));
end;
finally
Closefile(f);
end;
end;
Ok, on the other PCs you repeat the scan to build the list of files on that PC, you load the master list into another TStringlist, sort both lists and then compare them item by item. How complex that can get depends on what kinds of differences you expect to find. If there can be missing and extra files in addition to changed ones it gets a bit intricate but not too daunting. It goes like this:
You define two counters for the two lists, lets call them mi for the master list and li for the "local" list to compare it to. Both start out at 0.
while (mi < masterlist.count) do
begin
if masterlist[mi] = locallist[li] then
begin
{compare the two objects properties, if not equal report the file as changed}
Inc(mi);
Inc(li);
end
else if masterlist[mi] < locallist[li] then
begin
{report masterlist[mi] as missing}
Inc(mi);
end
else
begin
{report locallist[li] as extra}
Inc(li);
end;
if mi >= masterlist.count then
{report any remaining files in locallist as extra}
if li >= locallist.count then
{report any remaining files in masterlist as missing and increment mi for each,
so the loop is terminated}
end;
2005. február 5., szombat
How to autosize columns in a TDBGrid
Problem/Question/Abstract:
How to autosize columns in a TDBGrid
Answer:
This procedure will let you define the general layout of the grid at design-time by creating static columns for the grid, confident that proportions between columns will be maintained at run-time regardless of whether the user resizes the grid. To enable this new feature, disable column sizing for the grid (dgColSizing set to False in the grid options) and make a call to the new procedure in the OnResize event of the form holding the grid.
unit AdjustGrid;
interface
uses
Windows, Forms, DBGrids;
procedure AdjustColumnWidths(DBGrid: TDBGrid);
implementation
procedure AdjustColumnWidths(DBGrid: TDBGrid);
var
TotalColumnWidth, ColumnCount, GridClientWidth, Filler, i: Integer;
begin
ColumnCount := DBGrid.Columns.Count;
if ColumnCount = 0 then
Exit;
{compute total width used by grid columns and vertical lines if any}
TotalColumnWidth := 0;
for i := 0 to ColumnCount - 1 do
TotalColumnWidth := TotalColumnWidth + DBGrid.Columns[i].Width;
if dgColLines in DBGrid.Options then
{include vertical lines in total (one per column)}
TotalColumnWidth := TotalColumnWidth + ColumnCount;
{compute grid client width by excluding vertical scrollbar, grid indicator and grid border}
GridClientWidth := DBGrid.Width - GetSystemMetrics(SM_CXVSCROLL);
if dgIndicator in DBGrid.Options then
begin
GridClientWidth := GridClientWidth - IndicatorWidth;
if dgColLines in DBGrid.Options then
Dec(GridClientWidth);
end;
if DBGrid.BorderStyle = bsSingle then
begin
if DBGrid.Ctl3D then {border is sunken (vertical border is 2 pixels wide)}
GridClientWidth := GridClientWidth - 4
else {border is one-dimensional (vertical border is one pixel wide)}
GridClientWidth := GridClientWidth - 2;
end;
{adjust column widths}
if TotalColumnWidth < GridClientWidth then
begin
Filler := (GridClientWidth - TotalColumnWidth) div ColumnCount;
for i := 0 to ColumnCount - 1 do
DBGrid.Columns[i].Width := DBGrid.Columns[i].Width + Filler;
end
else if TotalColumnWidth > GridClientWidth then
begin
Filler := (TotalColumnWidth - GridClientWidth) div ColumnCount;
if (TotalColumnWidth - GridClientWidth) mod ColumnCount <> 0 then
Inc(Filler);
for i := 0 to ColumnCount - 1 do
DBGrid.Columns[i].Width := DBGrid.Columns[i].Width - Filler;
end;
end;
2005. február 4., péntek
Determine your LOCAL IP
Problem/Question/Abstract:
Determine your LOCAL IP
Answer:
Another piece of code to determine your machine's local IP number - the function GetLocalIP returns it as a string.
function LWToIP(LW: LongWord): string;
begin
Result := IntToStr(LW and $FF);
LW := LW shr 8;
Result := Result + '.' + IntToStr(LW and $FF);
LW := LW shr 8;
Result := Result + '.' + IntToStr(LW and $FF);
LW := LW shr 8;
Result := Result + '.' + IntToStr(LW and $FF);
end;
function TForm1.GetLocalIP: string;
var
name, A: PChar;
h: hostent;
I: Integer;
begin
GetMem(name, 255);
try
I := GetHostName(name, 255);
if I <> 0 then
I := wsagetlastError;
if I <> 0 then
StatusBar1.Panels[0].Text := 'Error: ' + IntToStr(I)
else
begin
h := GetHostByName(name)^;
if h.h_length <> 4 then
Result := ''
else
begin
A := h.h_addr_list^;
I := 0;
while (A^ <> #0) and (CompareStr(A, h.h_name) <> 0) do
begin
inc(I, 4);
Inc(A, 4)
end;
if I < 4 then
begin
Result := h.h_name
end
else
begin
while I >= 4 do
begin
Dec(A, 4);
Dec(I, 4);
Result := Result + LWToIP(PLongWord(A)^) + ', ';
end;
Delete(Result, Length(Result) - 1, 2);
end
end
end
finally
FreeMem(name)
end
end;
2005. február 3., csütörtök
PCX Image Component
Problem/Question/Abstract:
PCX image component. Fully supports reading and writing of: 1, 8 and 24 bit PCX images.
Answer:
///////////////////////////////////////////////////////////////////////
// //
// TPCXImage //
// ========= //
// //
// Completed: The 10th of August 2001 //
// Author: M. de Haan //
// Email: M.deHaan@inn.nl //
// Tested: under W95 SP1, NT4 SP6, WIN2000 //
// Version: 1.0 //
//-------------------------------------------------------------------//
// Update: The 14th of August 2001 to version 1.1. //
// Reason: Added version check. //
// Added comment info on version. //
// Changed PCX header ID check. //
//-------------------------------------------------------------------//
// Update: The 19th of August 2001 to version 2.0. //
// Reason: Warning from Delphi about using abstract methods, //
// caused by not implementing ALL TGraphic methods. //
// (Thanks goes to R.P. Sterkenburg for his diagnostic.) //
// Added: SaveToClipboardFormat, LoadFromClipboardFormat, //
// GetEmpty. //
//-------------------------------------------------------------------//
// Update: The 13th of October 2001 to version 2.1. //
// Reason: strange errors, read errors, EExternalException, IDE //
// hanging, Delphi hanging, Debugger hanging, windows //
// hanging, keyboard locked, and so on. //
// Changed: Assign procedure. //
//-------------------------------------------------------------------//
// Update: The 5th of April 2002 to version 2.2. //
// Changed: RLE compressor routine. //
// Reason: Incompatibility problems with other programs caused //
// by the RLE compressor. //
// Other programs encode: $C0 as: $C1 $C0. //
// ($C0 means: repeat the following byte 0 times //
// $C1 means: repeat the following byte 1 time.) //
// Changed: File read routine. //
// Reason: Now detects unsupported PCX data formats. //
// Added: 'Unsupported data format' in exception handler. //
// Added: 1 bit PCX support in reading. //
// Added: Procedure Convert1BitPCXDataToImage. //
// Renamed: Procedure ConvertPCXDataToImage to //
// Convert24BitPCXDataToImage. //
//-------------------------------------------------------------------//
// Update: The 14th of April 2002 to version 2.3. //
// Now capable of reading and writing 1 and 24 bit PCX //
// images. //
// Added: 1 bit PCX support in writing. //
// Added: Procedure ConvertImageTo1bitPCXData. //
// Changed: Procedure CreatePCXHeader. //
// Changed: Procedure TPCXImage.SaveToFile. //
//-------------------------------------------------------------------//
// Update: The 19th of April 2002 to version 2.4. //
// Now capable of reading and writing: 1, 8 and 24 bit //
// PCX images. //
// Added: 8 bit PCX support in reading and writing. //
// Renamed: Procedure ConvertImageTo1And8bitPCXData. //
// Renamed: Procedure Convert1And8bitPCXDataToImage. //
// Changed: Procedure fSetPalette, fGetPalette. //
//-------------------------------------------------------------------//
// Update: The 7th of May 2002 to version 2.5. //
// Reason: The palette of 8-bit PCX images couldn't be read in //
// the calling program. //
// Changed: Procedures Assign, AssignTo, fSetPalette, fGetPalette. //
// Tested: All formats were tested with the following programs: //
// - import in Word 97, //
// * (Word ignores the palette of 1 bit PCX images!) //
// - import and export in MigroGrafX. //
// * (MicroGrafX also ignores the palette of 1 bit PCX //
// images.) //
// No problems were detected. //
// //
//===================================================================//
// //
// The PCX image file format is copyrighted by: //
// ZSoft, PC Paintbrush, PC Paintbrush plus //
// Trademarks: N/A //
// Royalty fees: NONE //
// //
//===================================================================//
// //
// The author can not be held responsable for using this software //
// in anyway. //
// //
// The features and restrictions of this component are: //
// ---------------------------------------------------- //
// //
// The reading and writing (import / export) of files / images: //
// - PCX version 5 definition, PC Paintbrush 3 and higher, //
// - RLE-compressed, //
// - 1 and 8 bit PCX images WITH palette and //
// - 24 bit PCX images without palette, //
// are supported by this component. //
// //
// Known issues //
// ------------ //
// //
// 1) GetEmpty is NOT tested. //
// //
// 2) SaveToClipboardFormat is NOT tested. //
// //
// 3) LoadFromClipboardFormat is NOT tested. //
// //
// 4) 4 bit PCX images (with palette) are NOT (yet) implemented. //
// (I have no 4-bit PCX images to test it on...) //
// //
///////////////////////////////////////////////////////////////////////
unit
PCXImage;
interface
uses
Windows,
SysUtils,
Classes,
Graphics;
const
WIDTH_OUT_OF_RANGE = 'Illegal width entry in PCX file header';
HEIGHT_OUT_OF_RANGE = 'Illegal height entry in PCX file header';
FILE_FORMAT_ERROR = 'Invalid file format';
VERSION_ERROR = 'Only PC Paintbrush (plus) V3.0 and ' +
'higher are supported';
FORMAT_ERROR = 'Illegal identification byte in PCX file' +
' header';
PALETTE_ERROR = 'Invalid palette signature found';
ASSIGN_ERROR = 'Can only Assign a TBitmap or a TPicture';
ASSIGNTO_ERROR = 'Can only AssignTo a TBitmap';
PCXIMAGE_EMPTY = 'The PCX image is empty';
BITMAP_EMPTY = 'The bitmap is empty';
INPUT_FILE_TOO_LARGE = 'The input file is too large to be read';
IMAGE_WIDTH_TOO_LARGE = 'Width of PCX image is too large to handle';
// added 19/08/2001
CLIPBOARD_LOAD_ERROR = 'Loading from clipboard failed';
// added 19/08/2001
CLIPBOARD_SAVE_ERROR = 'Saving to clipboard failed';
// added 14/10/2001
PCX_WIDTH_ERROR = 'Unexpected line length in PCX data';
PCX_HEIGHT_ERROR = 'More PCX data found than expected';
PCXIMAGE_TOO_LARGE = 'PCX image is too large';
// added 5/4/2002
ERROR_UNSUPPORTED = 'Unsupported PCX format';
const
sPCXImageFile = 'PCX V3.0+ image';
// added 19/08/2001
var
CF_PCX: WORD;
///////////////////////////////////////////////////////////////////////
// //
// PCXHeader //
// //
///////////////////////////////////////////////////////////////////////
type
QWORD = Cardinal; // Seems more logical to me...
type
fColorEntry = packed record
ceRed: BYTE;
ceGreen: BYTE;
ceBlue: BYTE;
end; // of packed record fColorEntry
type
TPCXImageHeader = packed record
fID: BYTE;
fVersion: BYTE;
fCompressed: BYTE;
fBitsPerPixel: BYTE;
fWindow: packed record
wLeft,
wTop,
wRight,
wBottom: WORD;
end; // of packed record fWindow
fHorzResolution: WORD;
fVertResolution: WORD;
fColorMap: array[0..15] of fColorEntry;
fReserved: BYTE;
fPlanes: BYTE;
fBytesPerLine: WORD;
fPaletteInfo: WORD;
fFiller: array[0..57] of BYTE;
end; // of packed record TPCXImageHeader
///////////////////////////////////////////////////////////////////////
// //
// PCXData //
// //
///////////////////////////////////////////////////////////////////////
type
TPCXData = object
fData: array of BYTE;
end; // of Type TPCXData
///////////////////////////////////////////////////////////////////////
// //
// ScanLine //
// //
///////////////////////////////////////////////////////////////////////
const
fMaxScanLineLength = $FFF; // Max image width: 4096 pixels
type
mByteArray = array[0..fMaxScanLineLength] of BYTE;
pmByteArray = ^mByteArray;
// The "standard" pByteArray from Delphi allocates 32768 bytes,
// which is a little bit overdone here, I think...
const
fMaxImageWidth = $FFF; // Max image width: 4096 pixels
type
xByteArray = array[0..fMaxImageWidth] of BYTE;
///////////////////////////////////////////////////////////////////////
// //
// PCXPalette //
// //
///////////////////////////////////////////////////////////////////////
type
TPCXPalette = packed record
fSignature: BYTE;
fPalette: array[0..255] of fColorEntry;
end; // of packed record TPCXPalette
///////////////////////////////////////////////////////////////////////
// //
// Classes //
// //
///////////////////////////////////////////////////////////////////////
type
TPCXImage = class;
TPCXFile = class;
///////////////////////////////////////////////////////////////////////
// //
// PCXFile //
// //
// File handler //
// //
///////////////////////////////////////////////////////////////////////
TPCXFile = class(TPersistent)
private
fHeight: Integer;
fWidth: Integer;
fPCXHeader: TPCXImageHeader;
fPCXData: TPCXData;
fPCXPalette: TPCXPalette;
fColorDepth: QWORD;
fPixelFormat: BYTE; // added 5/4/2002
fCurrentPos: QWORD;
fHasPalette: Boolean; // added 7/5/2002
protected
// Protected declarations
public
// Public declarations
constructor Create;
destructor Destroy; override;
procedure LoadFromFile(const Filename: string);
procedure LoadFromStream(Stream: TStream);
procedure SaveToFile(const Filename: string);
procedure SaveToStream(Stream: TStream);
published
// Published declarations
// The publishing is done in the TPCXImage section
end;
///////////////////////////////////////////////////////////////////////
// //
// TPCXImage //
// //
// Image handler //
// //
///////////////////////////////////////////////////////////////////////
TPCXImage = class(TGraphic)
private
// Private declarations
fBitmap: TBitmap;
fPCXFile: TPCXFile;
fRLine: xByteArray;
fGLine: xByteArray;
fBLine: xByteArray;
fP: pmByteArray;
fhPAL: HPALETTE;
procedure fConvert24BitPCXDataToImage;
procedure fConvert1And8BitPCXDataToImage;
procedure fConvertImageTo24BitPCXData;
procedure fConvertImageTo1And8BitPCXData(ImageWidthInBytes:
QWORD);
procedure fFillDataLines(const fLine: array of BYTE);
procedure fCreatePCXHeader(const byBitsPerPixel: BYTE;
const byPlanes: BYTE; const wBytesPerLine: DWORD);
procedure fSetPalette(const wNumColors: WORD);
procedure fGetPalette(const wNumColors: WORD);
function fGetPixelFormat: TPixelFormat; // Added 07/05/2002
function fGetBitmap: TBitmap; // Added 07/05/2002
protected
// Protected declarations
procedure Draw(ACanvas: TCanvas; const Rect: TRect); override;
function GetHeight: Integer; override;
function GetWidth: Integer; override;
procedure SetHeight(Value: Integer); override;
procedure SetWidth(Value: Integer); override;
function GetEmpty: Boolean; override;
public
// Public declarations
constructor Create; override;
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
procedure AssignTo(Dest: TPersistent); override;
procedure LoadFromFile(const Filename: string); override;
procedure LoadFromStream(Stream: TStream); override;
procedure SaveToFile(const Filename: string); override;
procedure SaveToStream(Stream: TStream); override;
procedure LoadFromClipboardFormat(AFormat: WORD;
AData: THandle; APalette: HPALETTE); override;
procedure SaveToClipboardFormat(var AFormat: WORD;
var AData: THandle; var APalette: HPALETTE); override;
published
// Published declarations
property Height: Integer
read GetHeight write SetHeight;
property Width: Integer
read GetWidth write SetWidth;
property PixelFormat: TPixelFormat
read fGetPixelFormat;
property Bitmap: TBitmap
read fGetBitmap; // Added 7/5/2002
end;
implementation
///////////////////////////////////////////////////////////////////////
// //
// TPCXImage //
// //
// Image handler //
// //
///////////////////////////////////////////////////////////////////////
constructor TPCXImage.Create;
begin
inherited Create;
// Init HPALETTE
fhPAL := 0;
// Create a private bitmap to hold the image
if not Assigned(fBitmap) then
fBitmap := TBitmap.Create;
// Create the PCXFile
if not Assigned(fPCXFile) then
fPCXFile := TPCXFile.Create;
end;
//---------------------------------------------------------------------
destructor TPCXImage.Destroy;
begin
// Reversed order of create
// Free fPCXFile
fPCXFile.Free;
// Free private bitmap
fBitmap.Free;
// Delete palette
if fhPAL <> 0 then
DeleteObject(fhPAL);
// Distroy all the other things
inherited Destroy;
end;
//---------------------------------------------------------------------
procedure TPCXImage.SetHeight(Value: Integer);
begin
if Value >= 0 then
fBitmap.Height := Value;
end;
//---------------------------------------------------------------------
procedure TPCXImage.SetWidth(Value: Integer);
begin
if Value >= 0 then
fBitmap.Width := Value;
end;
//---------------------------------------------------------------------
function TPCXImage.GetHeight: Integer;
begin
Result := fPCXFile.fHeight;
end;
//---------------------------------------------------------------------
function TPCXImage.GetWidth: Integer;
begin
Result := fPCXFile.fWidth;
end;
//---------------------------------------------------------------------
function TPCXImage.fGetBitmap: TBitmap;
begin
Result := fBitmap;
end;
//-------------------------------------------------------------------//
// The credits for this procedure go to his work of TGIFImage by //
// Reinier P. Sterkenburg //
// Added 19/08/2001 //
//-------------------------------------------------------------------//
// NOT TESTED!
procedure TPCXImage.LoadFromClipboardFormat(AFormat: WORD;
ADAta: THandle; APalette: HPALETTE);
var
Size: QWORD;
Buf: Pointer;
Stream: TMemoryStream;
BMP: TBitmap;
begin
if (AData = 0) then
AData := GetClipBoardData(AFormat);
if (AData <> 0) and (AFormat = CF_PCX) then
begin
Size := GlobalSize(AData);
Buf := GlobalLock(AData);
try
Stream := TMemoryStream.Create;
try
Stream.SetSize(Size);
Move(Buf^, Stream.Memory^, Size);
Self.LoadFromStream(Stream);
finally
Stream.Free;
end;
finally
GlobalUnlock(AData);
end;
end
else if (AData <> 0) and (AFormat = CF_BITMAP) then
begin
BMP := TBitmap.Create;
try
BMP.LoadFromClipboardFormat(AFormat, AData, APalette);
Self.Assign(BMP);
finally
BMP.Free;
end;
end
else
raise Exception.Create(CLIPBOARD_LOAD_ERROR);
end;
//-------------------------------------------------------------------//
// The credits for this procedure go to his work of TGIFImage by //
// Reinier P. Sterkenburg //
// Added 19/08/2001 //
//-------------------------------------------------------------------//
// NOT TESTED!
procedure TPCXImage.SaveToClipboardFormat(var AFormat: WORD;
var AData: THandle; var APalette: HPALETTE);
var
Stream: TMemoryStream;
Data: THandle;
Buf: Pointer;
begin
if Empty then
Exit;
// First store the bitmap to the clipboard
fBitmap.SaveToClipboardFormat(AFormat, AData, APalette);
// Then try to save the PCX
Stream := TMemoryStream.Create;
try
SaveToStream(Stream);
Stream.Position := 0;
Data := GlobalAlloc(HeapAllocFlags, Stream.Size);
try
if Data <> 0 then
begin
Buf := GlobalLock(Data);
try
Move(Stream.Memory^, Buf^, Stream.Size);
finally
GlobalUnlock(Data);
end;
if SetClipBoardData(CF_PCX, Data) = 0 then
raise Exception.Create(CLIPBOARD_SAVE_ERROR);
end;
except
GlobalFree(Data);
raise;
end;
finally
Stream.Free;
end;
end;
//-------------------------------------------------------------------//
// NOT TESTED!
function TPCXImage.GetEmpty: Boolean; // Added 19/08/2002
begin
if Assigned(fBitmap) then
Result := fBitmap.Empty
else
Result := (fPCXFile.fHeight = 0) or (fPCXFile.fWidth = 0);
end;
//---------------------------------------------------------------------
procedure TPCXImage.SaveToFile(const Filename: string);
var
fPCX: TFileStream;
W, WW: QWORD;
begin
if (fBitmap.Width = 0) or (fBitmap.Height = 0) then
raise Exception.Create(BITMAP_EMPTY);
W := fBitmap.Width;
WW := W div 8;
if (W mod 8) > 0 then
Inc(WW);
case fBitmap.PixelFormat of
pf1bit:
begin
// Fully supported by PCX and by this component
fCreatePCXHeader(1, 1, WW);
fConvertImageTo1And8BitPCXData(WW);
fGetPalette(2);
end;
pf4bit:
begin
// I don't have 4-bit PCX images to test with
// It will be treated as a 24 bit image
fCreatePCXHeader(8, 3, W);
fConvertImageTo24BitPCXData;
end;
pf8bit:
begin
// Fully supported by PCX and by this component
fCreatePCXHeader(8, 1, W);
fConvertImageTo1And8BitPCXData(W);
fGetPalette(256);
end;
pf15bit:
begin
// Is this supported in PCX?
// It will be treated as a 24 bit image
fCreatePCXHeader(8, 3, W);
fConvertImageTo24BitPCXData;
end;
pf16bit:
begin
// Is this supported in PCX?
// It will be treated as a 24 bit image
fCreatePCXHeader(8, 3, W);
fConvertImageTo24BitPCXData;
end;
pf24bit:
begin
// Fully supported by PCX and by this component
fCreatePCXHeader(8, 3, W);
fConvertImageTo24BitPCXData;
end;
pf32bit:
begin
// Not supported by PCX
fCreatePCXHeader(8, 3, W);
fConvertImageTo24BitPCXData;
end;
else
begin
fCreatePCXHeader(8, 3, W);
fConvertImageTo24BitPCXData;
end; // of else
end; // of Case
fPCX := TFileStream.Create(Filename, fmCreate);
try
fPCX.Position := 0;
SaveToStream(fPCX);
finally
fPCX.Free;
end; // of finally
SetLength(fPCXFile.fPCXData.fData, 0);
end; // of Procedure SaveToFile
//-------------------------------------------------------------------//
procedure TPCXImage.AssignTo(Dest: TPersistent);
var
bAssignToError: Boolean;
begin
bAssignToError := True;
if Dest is TBitmap then
begin
// The old AssignTo procedure was like this.
// But then the palette was couldn't be accessed in the calling
// program for some reason.
// --------------------------
// (Dest as TBitmap).Assign(fBitmap);
// If fBitmap.Palette <> 0 then
// (Dest as TBitmap).Palette := CopyPalette(fBitmap.Palette);
// --------------------------
// Do the assigning
(Dest as TBitmap).Assign(fBitmap);
if fPCXFile.fHasPalette then
(Dest as TBitmap).Palette := CopyPalette(fhPAL);
// Now the calling program can access the palette
// (if it has one)!
bAssignToError := False;
end;
if Dest is TPicture then
begin
(Dest as TPicture).Graphic.Assign(fBitmap);
bAssignToError := False;
end;
if bAssignToError then
raise Exception.Create(ASSIGNTO_ERROR);
// You can write other assignments here, if you want...
end;
//-------------------------------------------------------------------//
procedure TPCXImage.Assign(Source: TPersistent);
var
iX, iY: DWORD;
bAssignError: Boolean;
begin
bAssignError := True;
if (Source is TBitmap) then
begin
fBitmap.Assign(Source as TBitmap);
if (Source as TBitmap).Palette <> 0 then
begin
fhPAL := CopyPalette((Source as TBitmap).Palette);
fBitmap.Palette := fhPAL;
end;
bAssignError := False;
end;
if (Source is TPicture) then
begin
iX := (Source as TPicture).Width;
iY := (Source as TPicture).Height;
fBitmap.Width := iX;
fBitmap.Height := iY;
fBitmap.Canvas.Draw(0, 0, (Source as TPicture).Graphic);
bAssignError := False;
end;
// You can write other assignments here, if you want...
if bAssignError then
raise Exception.Create(ASSIGN_ERROR);
end;
//---------------------------------------------------------------------
procedure TPCXImage.Draw(ACanvas: TCanvas; const Rect: TRect);
begin
// Faster
// ACanvas.Draw(0,0,fBitmap);
// Slower
ACanvas.StretchDraw(Rect, fBitmap);
end;
//---------------------------------------------------------------------
procedure TPCXImage.LoadFromFile(const Filename: string);
begin
fPCXFile.LoadFromFile(Filename);
// added 5/4/2002
case fPCXFile.fPixelFormat of
1: fConvert1And8BitPCXDataToImage;
8: fConvert1And8BitPCXDataToImage;
24: fConvert24BitPCXDataToImage;
end;
end;
//---------------------------------------------------------------------
procedure TPCXImage.SaveToStream(Stream: TStream);
begin
fPCXFile.SaveToStream(Stream);
end;
//---------------------------------------------------------------------
procedure TPCXImage.LoadFromStream(Stream: TStream);
begin
fPCXFile.LoadFromStream(Stream);
end;
///////////////////////////////////////////////////////////////////////
// //
// Called by RLE compressor //
// //
///////////////////////////////////////////////////////////////////////
procedure TPCXImage.fFillDataLines(const fLine: array of BYTE);
var
By: BYTE;
Cnt: WORD;
I: QWORD;
W: QWORD;
begin
I := 0;
By := fLine[0];
Cnt := $C1;
W := fBitmap.Width;
repeat
Inc(I);
if By = fLine[I] then
begin
Inc(Cnt);
if Cnt = $100 then
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] :=
BYTE(Pred(Cnt));
Inc(fPCXFile.fCurrentPos);
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := By;
Inc(fPCXFile.fCurrentPos);
Cnt := $C1;
By := fLine[I];
end;
end;
if (By <> fLine[I]) then
begin
if (Cnt = $C1) then
begin
// If (By < $C1) then
if (By < $C0) then // changed 5/4/2002
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := By;
Inc(fPCXFile.fCurrentPos);
end
else
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := BYTE(Cnt);
Inc(fPCXFile.fCurrentPos);
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := By;
Inc(fPCXFile.fCurrentPos);
end;
end
else
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := BYTE(Cnt);
Inc(fPCXFile.fCurrentPos);
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := By;
Inc(fPCXFile.fCurrentPos);
end;
Cnt := $C1;
By := fLine[I];
end;
until I = W - 1;
// Write the last byte(s)
if (Cnt > $C1) then
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := BYTE(Cnt);
Inc(fPCXFile.fCurrentPos);
end;
if (Cnt = $C1) and (By > $C0) then
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := BYTE(Cnt);
Inc(fPCXFile.fCurrentPos);
end;
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := By;
Inc(fPCXFile.fCurrentPos);
end;
//-------------------------------------------------------------------//
// RLE Compression algorithm //
//-------------------------------------------------------------------//
procedure TPCXImage.fConvertImageTo24BitPCXData; // Renamed 5/4/2002
var
H, W: QWORD;
X, Y: QWORD;
I: QWORD;
begin
H := fBitmap.Height;
W := fBitmap.Width;
fPCXFile.fCurrentPos := 0;
SetLength(fPCXFile.fPCXData.fData, 6 * H * W); // To be sure...
fBitmap.PixelFormat := pf24bit; // Always do this if you're using
// ScanLine!
for Y := 0 to H - 1 do
begin
fP := fBitmap.ScanLine[Y];
I := 0;
for X := 0 to W - 1 do
begin
fRLine[X] := fP[I];
Inc(I); // Extract a red line
fGLine[X] := fP[I];
Inc(I); // Extract a green line
fBLine[X] := fP[I];
Inc(I); // Extract a blue line
end;
fFillDataLines(fBLine); // Compress the blue line
fFillDataLines(fGLine); // Compress the green line
fFillDataLines(fRLine); // Compress the red line
end;
// Correct the length of fPCXData.fData
SetLength(fPCXFile.fPCXData.fData, fPCXFile.fCurrentPos);
end;
//-------------------------------------------------------------------//
procedure TPCXImage.fConvertImageTo1And8BitPCXData(ImageWidthInBytes:
QWORD);
var
H, W, X, Y: QWORD;
oldByte, newByte: BYTE;
Cnt: BYTE;
begin
H := fBitmap.Height;
W := ImageWidthInBytes;
fPCXFile.fCurrentPos := 0;
SetLength(fPCXFile.fPCXData.fData, 2 * H * W); // To be sure...
oldByte := 0; // Otherwise the compiler issues a warning about
// oldByte not being initialized...
Cnt := $C1;
for Y := 0 to H - 1 do
begin
fP := fBitmap.ScanLine[Y];
for X := 0 to W - 1 do
begin
newByte := fP[X];
if X > 0 then
begin
if (Cnt = $FF) then
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := Cnt;
Inc(fPCXFile.fCurrentPos);
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := oldByte;
Inc(fPCXFile.fCurrentPos);
Cnt := $C1;
end
else if newByte = oldByte then
Inc(Cnt);
if newByte <> oldByte then
begin
if (Cnt > $C1) or (oldByte >= $C0) then
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := Cnt;
Inc(fPCXFile.fCurrentPos);
Cnt := $C1;
end;
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := oldByte;
Inc(fPCXFile.fCurrentPos);
end;
end;
oldByte := newByte;
end;
// Write last byte of line
if (Cnt > $C1) or (oldByte >= $C0) then
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := Cnt;
Inc(fPCXFile.fCurrentPos);
Cnt := $C1;
end;
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := oldByte;
Inc(fPCXFile.fCurrentPos);
end;
// Write last byte of image
if (Cnt > $C1) or (oldByte >= $C0) then
begin
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := Cnt;
Inc(fPCXFile.fCurrentPos);
// Cnt := 1;
end;
fPCXFile.fPCXData.fData[fPCXFile.fCurrentPos] := oldByte;
Inc(fPCXFile.fCurrentPos);
// Correct the length of fPCXData.fData
SetLength(fPCXFile.fPCXData.fData, fPCXFile.fCurrentPos);
end;
//-------------------------------------------------------------------//
// RLE Decompression algorithm //
//-------------------------------------------------------------------//
procedure TPCXImage.fConvert24BitPCXDataToImage; // Renamed 5/4/2002
var
I: QWORD;
By: BYTE;
Cnt: BYTE;
H, W: QWORD;
X, Y: QWORD;
K, L: QWORD;
begin
H := fPCXFile.fPCXHeader.fWindow.wBottom -
fPCXFile.fPCXHeader.fWindow.wTop + 1;
W := fPCXFile.fPCXHeader.fWindow.wRight -
fPCXFile.fPCXHeader.fWindow.wLeft + 1;
Y := 0; // First line of image
fBitmap.Width := W; // Set bitmap width
fBitmap.Height := H; // Set bitmap height
fBitmap.PixelFormat := pf24bit; // Always do this if you're using
// ScanLine!
I := 0; // Pointer to data byte of fPXCFile
repeat
// Process the red line
// ProcessLine(fRLine,W);
X := 0; // Pointer to position in Red / Green / Blue line
repeat
By := fPCXFile.fPCXData.fData[I];
Inc(I);
// one byte
if By < $C1 then
if X <= W then // added 5/4/2002
begin
fRLine[X] := By;
Inc(X);
end;
// multiple bytes (RLE)
if By > $C0 then
begin
Cnt := By and $3F;
By := fPCXFile.fPCXData.fData[I];
Inc(I);
//FillChar(fRLine[J],Cnt,By);
//Inc(J,Cnt);
for K := 1 to Cnt do
if X <= W then // added 5/4/2002
begin
fRLine[X] := By;
Inc(X);
end;
end;
until X >= W;
// Process the green line
// ProcessLine(fGLine,W);
X := 0;
repeat
By := fPCXFile.fPCXData.fData[I];
Inc(I);
// one byte
if By < $C1 then
if X <= W then // added 5/4/2002
begin
fGLine[X] := By;
Inc(X);
end;
// multiple bytes (RLE)
if By > $C0 then
begin
Cnt := By and $3F;
By := fPCXFile.fPCXData.fData[I];
Inc(I);
for K := 1 to Cnt do
if X <= W then // added 5/4/2002
begin
fGLine[X] := By;
Inc(X);
end;
end;
until X >= W;
// Process the blue line
// ProcessLine(fBLine,W);
X := 0;
repeat
By := fPCXFile.fPCXData.fData[I];
Inc(I);
// one byte
if By < $C1 then
if X <= W then // added 5/4/2002
begin
fBLine[X] := By;
Inc(X);
end;
// multiple bytes (RLE)
if By > $C0 then
begin
Cnt := By and $3F;
By := fPCXFile.fPCXData.fData[I];
Inc(I);
for K := 1 to Cnt do
if X <= W then // added 5/4/2002
begin
fBLine[X] := By;
Inc(X);
end;
end;
until X >= W;
// Write the just processed data RGB lines to the bitmap
fP := fBitmap.ScanLine[Y];
L := 0;
for X := 0 to W - 1 do
begin
fP[L] := fBLine[X];
Inc(L);
fP[L] := fGLine[X];
Inc(L);
fP[L] := fRLine[X];
Inc(L);
end;
Inc(Y); // Process the next RGB line
until Y >= H;
SetLength(fPCXFile.fPCXData.fData, 0);
end;
//-------------------------------------------------------------------//
procedure TPCXImage.fConvert1And8BitPCXDataToImage; // added 5/4/2002
var
I, J: QWORD;
By: BYTE;
Cnt: BYTE;
H, W, WW: QWORD;
X, Y: QWORD;
begin
H := fPCXFile.fPCXHeader.fWindow.wBottom -
fPCXFile.fPCXHeader.fWindow.wTop + 1;
W := fPCXFile.fPCXHeader.fWindow.wRight -
fPCXFile.fPCXHeader.fWindow.wLeft + 1;
fBitmap.Width := W; // Set bitmap width
fBitmap.Height := H; // Set bitmap height
WW := W;
// 1 bit PCX
if fPCXFile.fPixelFormat = 1 then
begin
// All 1 bit images have a palette
fBitmap.PixelFormat := pf1bit; // Always do this if you're using
// ScanLine!
WW := W div 8; // Correct width for pf1bit
if W mod 8 > 0 then
begin
Inc(WW);
fBitMap.Width := WW * 8;
end;
fSetPalette(2);
end;
// 8 bit PCX
if fPCXFile.fPixelFormat = 8 then
begin
// All 8 bit images have a palette!
// This is how to set the palette of a bitmap
// 1. First set the bitmap to pf8bit;
// 2. then set the palette of the bitmap;
// 3. then set the pixels with ScanLine or with Draw.
// If you do it with StretchDraw, it won't work. Don't ask me why.
// If you don't do it in this order, it won't work either! You'll
// get strange colors.
fBitmap.PixelFormat := pf8bit; // Always do this if you're using
// ScanLine!
fSetPalette(256);
end;
I := 0;
Y := 0;
repeat
fP := fBitmap.ScanLine[Y];
X := 0; // Pointer to position in line
repeat
By := fPCXFile.fPCXData.fData[I];
Inc(I);
// one byte
if By < $C1 then
if X <= WW then
begin
fP[X] := By;
Inc(X);
end;
// multiple bytes (RLE)
if By > $C0 then
begin
Cnt := By and $3F;
By := fPCXFile.fPCXData.fData[I];
Inc(I);
for J := 1 to Cnt do
if X <= WW then
begin
fP[X] := By;
Inc(X);
end;
end;
until X >= WW;
Inc(Y); // Next line
until Y >= H;
end;
//---------------------------------------------------------------------
procedure TPCXImage.fCreatePCXHeader(const byBitsPerPixel: BYTE;
const byPlanes: BYTE; const wBytesPerLine: DWORD);
var
H, W: WORD;
begin
W := fBitmap.Width;
H := fBitmap.Height;
// PCX header
fPCXFile.fPCXHeader.fID := BYTE($0A); // BYTE (1)
fPCXFile.fPCXHeader.fVersion := BYTE(5); // BYTE (2)
fPCXFile.fPCXHeader.fCompressed := BYTE(1); // BYTE (3)
// 0 = uncompressed, 1 = compressed
// Only RLE compressed files are supported by this component
fPCXFile.fPCXHeader.fBitsPerPixel := BYTE(byBitsPerPixel);
// BYTE (4)
fPCXFile.fPCXHeader.fWindow.wLeft := WORD(0); // WORD (5,6)
fPCXFile.fPCXHeader.fWindow.wTop := WORD(0); // WORD (7,8)
fPCXFile.fPCXHeader.fWindow.wRight := WORD(W - 1); // WORD (9,10)
fPCXFile.fPCXHeader.fWindow.wBottom := WORD(H - 1); // WORD (11,12)
fPCXFile.fPCXHeader.fHorzResolution := WORD(72); // WORD (13,14)
fPCXFile.fPCXHeader.fVertResolution := WORD(72); // WORD (15,16)
FillChar(fPCXFile.fPCXHeader.fColorMap, 48, 0); // Array of Byte
// (17..64)
fPCXFile.fPCXHeader.fReserved := BYTE(0); // BYTE (65)
fPCXFile.fPCXHeader.fPlanes := BYTE(byPlanes);
// BYTE (66)
fPCXFile.fPCXHeader.fBytesPerLine := WORD(wBytesPerLine);
// WORD (67,68)
// must be even
// rounded above
fPCXFile.fPCXHeader.fPaletteInfo := WORD(1); // WORD (69,70)
FillChar(fPCXFile.fPCXHeader.fFiller, 58, 0); // Array of Byte
// (71..128)
fPCXFile.fPixelFormat := fPCXFile.fPCXHeader.fPlanes *
fPCXFile.fPCXHeader.fBitsPerPixel;
fPCXFile.fColorDepth := 1 shl fPCXFile.fPixelFormat;
end;
//---------------------------------------------------------------------
(*
// From Delphi 5.0, graphics.pas
Function CopyPalette(Palette: HPALETTE): HPALETTE;
Var
PaletteSize : Integer;
LogPal : TMaxLogPalette;
Begin
Result := 0;
If Palette = 0 then
Exit;
PaletteSize := 0;
If GetObject(Palette,SizeOf(PaletteSize),@PaletteSize) = 0 then
Exit;
If PaletteSize = 0 then
Exit;
With LogPal do
Begin
palVersion := $0300;
palNumEntries := PaletteSize;
GetPaletteEntries(Palette,0,PaletteSize,palPalEntry);
End;
Result := CreatePalette(PLogPalette(@LogPal)^);
End;
*)
//---------------------------------------------------------------------
// From Delphi 5.0, graphics.pas
(*
Procedure TPCXImage.fSetPixelFormat(Value : TPixelFormat);
Const
BitCounts : Array [pf1Bit..pf32Bit] of BYTE = (1,4,8,16,16,24,32);
Var
DIB : TDIBSection;
Pal : HPALETTE;
DC : hDC;
KillPal : Boolean;
Begin
If Value = GetPixelFormat then
Exit;
Case Value of
pfDevice : Begin
HandleType := bmDDB;
Exit;
End;
pfCustom : InvalidGraphic(@SInvalidPixelFormat);
else
FillChar(DIB,sizeof(DIB), 0);
DIB.dsbm := FImage.FDIB.dsbm;
KillPal := False;
With DIB, dsbm,dsbmih do
Begin
bmBits := nil;
biSize := SizeOf(DIB.dsbmih);
biWidth := bmWidth;
biHeight := bmHeight;
biPlanes := 1;
biBitCount := BitCounts[Value];
Pal := FImage.FPalette;
Case Value of
pf4Bit : Pal := SystemPalette16;
pf8Bit : Begin
DC := GDICheck(GetDC(0));
Pal := CreateHalftonePalette(DC);
KillPal := True;
ReleaseDC(0, DC);
End;
pf16Bit : Begin
biCompression := BI_BITFIELDS;
dsBitFields[0] := $F800;
dsBitFields[1] := $07E0;
dsBitFields[2] := $001F;
End;
End; // of Case
Try
CopyImage(Handle, Pal, DIB);
PaletteModified := (Pal <> 0);
Finally
if KillPal then
DeleteObject(Pal);
End; // of Try
Changed(Self);
End; // of With
End; // of Case
End; // of Procedure
*)
//---------------------------------------------------------------------
procedure TPCXImage.fSetPalette(const wNumColors: WORD);
(* From Delphi 5.0, graphics.pas
Type
TPalEntry = packed record
peRed : BYTE;
peGreen : BYTE;
peBlue : BYTE;
End;
Type
tagLOGPALETTE = packed record
palVersion : WORD;
palNumEntries : WORD;
palPalEntry : Array[0..255] of TPalEntry
End;
Type
TMAXLogPalette = tagLOGPALETTE;
PMAXLogPalette = ^TMAXLogPalette;
Type
PRGBQuadArray = ^TRGBQuadArray;
TRGBQuadArray = Array[BYTE] of TRGBQuad;
Type
PRGBQuadArray = ^TRGBQuadArray;
TRGBQuadArray = Array[BYTE] of TRGBQuad;
*)
var
pal: TMaxLogPalette;
W: WORD;
begin
pal.palVersion := $300; // The "Magic" number
pal.palNumEntries := wNumColors;
for W := 0 to 255 do
begin
pal.palPalEntry[W].peRed :=
fPCXFile.fPCXPalette.fPalette[W].ceRed;
pal.palPalEntry[W].peGreen :=
fPCXFile.fPCXPalette.fPalette[W].ceGreen;
pal.palPalEntry[W].peBlue :=
fPCXFile.fPCXPalette.fPalette[W].ceBlue;
pal.palPalEntry[W].peFlags := 0;
end;
(* Must we delete the old palette first here? I don't know.
If fhPAL <> 0 then
DeleteObject(fhPAL);
*)
fhPAL := CreatePalette(PLogPalette(@pal)^);
if fhPAL <> 0 then
fBitmap.Palette := fhPAL;
end;
//---------------------------------------------------------------------
function TPCXImage.fGetPixelFormat: TPixelFormat;
// Only pf1bit, pf4bit and pf8bit images have a palette.
// pf15bit, pf16bit, pf24bit and pf32bit images have no palette.
// You can change the palette of pf1bit images in windows.
// The foreground color and the background color of pf1bit images
// do not have to be black and white. You can choose any tow colors.
// The palette of pf4bit images is fixed.
// The palette entries 0..9 and 240..255 of pf8bit images are reserved
// in windows.
begin
Result := pfDevice;
case fPCXFile.fPixelFormat of
01: Result := pf1bit; // Implemented WITH palette.
// 04 : Result := pf4bit; // Not yet implemented in this component,
// is however implemented in PCX format.
08: Result := pf8bit; // Implemented WITH palette.
// 15 : Result := pf15bit; // Not implemented in PCX format?
// 16 : Result := pf16bit; // Not implemented in PCX format?
24: Result := pf24bit; // Implemented, has no palette.
// 32 : Result := pf32bit; // Not implemented in PCX format.
end;
end;
//---------------------------------------------------------------------
procedure TPCXImage.fGetPalette(const wNumColors: WORD);
var
pal: TMaxLogPalette;
W: WORD;
begin
fPCXFile.fPCXPalette.fSignature := $0C;
pal.palVersion := $300; // The "Magic" number
pal.palNumEntries := wNumColors;
GetPaletteEntries(CopyPalette(fBitmap.Palette), 0, wNumColors,
pal.palPalEntry);
for W := 0 to 255 do
if W < wNumColors then
begin
fPCXFile.fPCXPalette.fPalette[W].ceRed :=
pal.palPalEntry[W].peRed;
fPCXFile.fPCXPalette.fPalette[W].ceGreen :=
pal.palPalEntry[W].peGreen;
fPCXFile.fPCXPalette.fPalette[W].ceBlue :=
pal.palPalEntry[W].peBlue;
end
else
begin
fPCXFile.fPCXPalette.fPalette[W].ceRed := 0;
fPCXFile.fPCXPalette.fPalette[W].ceGreen := 0;
fPCXFile.fPCXPalette.fPalette[W].ceBlue := 0;
end;
end;
//=====================================================================
///////////////////////////////////////////////////////////////////////
// //
// TPCXFile //
// //
///////////////////////////////////////////////////////////////////////
constructor TPCXFile.Create;
begin
inherited Create;
fHeight := 0;
fWidth := 0;
fCurrentPos := 0;
end;
//---------------------------------------------------------------------
destructor TPCXFile.Destroy;
begin
SetLength(fPCXData.fData, 0);
inherited Destroy;
end;
//---------------------------------------------------------------------
procedure TPCXFile.LoadFromFile(const Filename: string);
var
fPCXStream: TFileStream;
begin
fPCXStream := TFileStream.Create(Filename, fmOpenRead);
try
fPCXStream.Position := 0;
LoadFromStream(fPCXStream);
finally
fPCXStream.Free;
end;
end;
//---------------------------------------------------------------------
procedure TPCXFile.SaveToFile(const Filename: string);
var
fPCXStream: TFileStream;
begin
fPCXStream := TFileStream.Create(Filename, fmCreate);
try
fPCXStream.Position := 0;
SaveToStream(fPCXStream);
finally
fPCXStream.Free;
end;
end;
//---------------------------------------------------------------------
procedure TPCXFile.LoadFromStream(Stream: TStream);
var
fFileLength: Cardinal;
begin
// Read the PCX header
Stream.Read(fPCXHeader, SizeOf(fPCXHeader));
// Check the ID byte
if fPCXHeader.fID <> $0A then
raise Exception.Create(FORMAT_ERROR);
(*
Check PCX version byte
======================
Versionbyte = 0 => PC PaintBrush V2.5
Versionbyte = 2 => PC Paintbrush V2.8 with palette information
Versionbyte = 3 => PC Paintbrush V2.8 without palette information
Versionbyte = 4 => PC Paintbrush for Windows
Versionbyte = 5 => PC Paintbrush V3 and up, and PC Paintbrush Plus
with 24 bit image support
*)
// Check the PCX version
if fPCXHeader.fVersion <> 5 then
raise Exception.Create(VERSION_ERROR);
// Calculate width
fWidth := fPCXHeader.fWindow.wRight - fPCXHeader.fWindow.wLeft + 1;
if fWidth < 0 then
raise Exception.Create(WIDTH_OUT_OF_RANGE);
// Calculate height
fHeight := fPCXHeader.fWindow.wBottom - fPCXHeader.fWindow.wTop + 1;
if fHeight < 0 then
raise Exception.Create(HEIGHT_OUT_OF_RANGE);
// Is it too large?
if fWidth > fMaxImageWidth then
raise Exception.Create(IMAGE_WIDTH_TOO_LARGE);
// Calculate pixelformat
fPixelFormat := fPCXHeader.fPlanes * fPCXHeader.fBitsPerPixel;
// Calculate number of colors
fColorDepth := 1 shl fPixelFormat;
// Is this image supported?
if not (fPixelFormat in [1, 8, 24]) then
raise Exception.Create(ERROR_UNSUPPORTED);
// The lines following are NOT tested!!!
(*
If fColorDepth <= 16 then
For I := 0 to fColorDepth - 1 do
Begin
If fPCXHeader.fVersion = 3 then
Begin
fPCXPalette.fPalette[I].R := fPCXHeader.fColorMap[I].R shl 2;
fPCXPalette.fPalette[I].G := fPCXHeader.fColorMap[I].G shl 2;
fPCXPalette.fPalette[I].B := fPCXHeader.fColorMap[I].B shl 2;
End
else
Begin
fPCXPalette.fPalette[I].R := fPCXHeader.fColorMap[I].R;
fPCXPalette.fPalette[I].G := fPCXHeader.fColorMap[I].G;
fPCXPalette.fPalette[I].B := fPCXHeader.fColorMap[I].B;
End;
End;
*)
// Calculate number of data bytes
// If fFileLength > fMaxDataFileLength then
// Raise Exception.Create(INPUT_FILE_TOO_LARGE);
if fPixelFormat = 24 then
begin
fFileLength := Stream.Size - Stream.Position;
SetLength(fPCXData.fData, fFileLength);
// Read the data
Stream.Read(fPCXData.fData[0], fFileLength);
fHasPalette := False;
end;
if fPixelFormat in [1, 8] then
begin
fFileLength := Stream.Size - Stream.Position - 769;
SetLength(fPCXData.fData, fFileLength);
// Correct number of data bytes
Stream.Read(fPCXData.fData[0], fFilelength);
// Read the palette
Stream.Read(fPCXPalette, SizeOf(fPCXPalette));
fHasPalette := True;
// Check palette signature byte
if fPCXPalette.fSignature <> $0C then
raise Exception.Create(PALETTE_ERROR);
end;
end;
//---------------------------------------------------------------------
procedure TPCXFile.SaveToStream(Stream: TStream);
begin
fHasPalette := False;
Stream.Write(fPCXHeader, SizeOf(fPCXHeader));
Stream.Write(fPCXData.fData[0], fCurrentPos);
if fPixelFormat in [1, 8] then
begin
Stream.Write(fPCXPalette, SizeOf(fPCXPalette));
fHasPalette := True;
end;
end;
//---------------------------------------------------------------------
// Register PCX format
initialization
TPicture.RegisterFileFormat('PCX', sPCXImageFile, TPCXImage);
CF_PCX := RegisterClipBoardFormat('PCX Image');
TPicture.RegisterClipBoardFormat(CF_PCX, TPCXImage);
//---------------------------------------------------------------------
// Unregister PCX format
finalization
TPicture.UnRegisterGraphicClass(TPCXImage);
//---------------------------------------------------------------------
end.
//=====================================================================
Feliratkozás:
Bejegyzések (Atom)