2008. március 31., hétfő
Detect and download a new version
Problem/Question/Abstract:
How can I detect and download a new version of my applications.
Answer:
{Well first you need a home page or a server and then you must use the following source code}
uses URLMON, INIFILES;
{This will download the necesary files}
function DownloadFile(Source, Dest: string): Boolean;
begin
try
Result := UrlDownloadToFile(nil, PChar(source), PChar(Dest), 0, nil) = 0;
except
Result := False;
end;
end;
function GetPathPath: string;
begin
Result := ExtractFilePath(Paramstr(0)); //This get's the path
end;
{You need to set up on your server a ini file called update.ini containing the following lines}
[version]
wsc=6
then apply this source code:
var
apath: string;
new: Integer;
begin
apath := GetPathPath;
Gauge1.Progress := 0;
StatusBar1.SimplePanel := True;
StatusBar1.SimpleText := 'Connecting to http://tsoft.home.ro';
Gauge1.Progress := 20;
if DownloadFile('http://www.tsoft.home.ro/update.ini', PChar(apath) + '/update.ini')
then
begin
Gauge1.Progress := 50;
StatusBAr1.SimplePanel := True;
StatusBar1.SimpleText := 'Checking for newer versions...';
vernfo := TiniFile.Create(GetPathPath + '/update.ini');
new := vernfo.ReadInteger('version', 'wsc', 6);
vernfo.Free;
if (old = new) then
begin
StatusBar1.SimplePanel := True;
StatusBar1.SimpleText := 'No new version detected';
Gauge1.Progress := 100;
end
else if DownloadFile('http://www.tsoft.home.ro/winnew.exe', PChar(apath) +
'/winsafe.exe') then
begin
ShowMessage('Update succeseful');
Gauge1.Progress := 100;
winsc := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
winsc.WriteInteger('wsc', 'vernfo', new);
winsc.Free;
end
else
MessageDlg('A new version has appeard but it requires a second istall!',
mtInformation, [mbOk], 0);
end
else
begin
StatusBAr1.SimplePanel := True;
StatusBar1.SimpleText := 'Failed probably a internet problem';
Gauge1.Progress := 0;
end;
end;
{You will figure it out coz you can't copy the code like this you must make a few adjustments but I provided the ideea}
2008. március 30., vasárnap
Microsoft automation: dataset export and printing
Problem/Question/Abstract:
There are many ways one can view the Automation. The most pragmatic one is the following: parts of your application already exist on the client’s machine. Writing them again is a waste of time. Creating a new program is maybe like creating the universe, but the privilege to start from scratch every time is reserved only for God. Using Microsoft Automation, though, is almost as exciting: a lot of hidden surprises and riffs are waiting for you and sometimes the only way forward is to experiment. If you have enough perserverance to cope with the constantly changing Microsoft environment, success will come to you – a truth, which is applicable not only to programming …
Answer:
Delphi 5 makes the task perhaps slightly easier: a set of nice server components are on the palette. It is up to you if you want to use them or not, but you should be aware of one important thing: they are not real Delphi components. An imported type library is hidden behind them and often is very useful to know which one it is.
The Office 2000 object model is different from the Office 97 one. As my experience shows it is still more advisable to use the older library. Otherwise, you have to make sure that all your clients have Office 2000 installed. Moreover, it is easy to “rewrite” the server components only in a few minutes: remove the package and import the desired library into a new one. In my case I use the Excel 97 library. I have tested it in Office 2000 environment with no problems.
As s for components it is just more convinient to use them instead of calling the interfaces directly. The wrapper is too thin to disturb the performance but if you have concerns you can combine both approaches. Sometimes even using Variants is unavoidable .
CELL BY CELL
Automating Excel is one of the most efficient ways to have a DBGrid or a Dataset printed. It is easy to import data to Excel and the options for formatting, adding calculated fields, summaries or even charts are almost unlimited. Excel can be a very powerfull report generator for any application.
The most obvious approach is to fill the Excel worksheet as a stringgrid: cell by cell. The field datatype and even the value for each cell can be checked during this operation and formatted accordingly.
The first task is to connect to a new Excel worksheet. I use 3 components to accomplish this:
Excel: TExcelApplication;
Worksheet: TExcelWorksheet;
Workbook: TExcelWorkbook;
This follows the logics of the Excel’s object model. Theoretically, you should be able to connect the worksheet component directly . In practice even using the three components can be problematic: you can not connect the worksheet before opening the workbook and at least on my machine every attempt to open a workbook would cause an error. Thanks to Deborah Pate I already know how to prevent this:
Excel.Connect;
lcid := GetUserDefaultLCID;
Workbook.ConnectTo(Excel.Workbooks.Add(TOleEnum(xlWBATWorksheet), lcid);
Worksheet.ConnectTo(Workbook.Worksheets[1] as _Worksheet);
Now the new worksheet is ready for filling. If you wold like to see it at this point, add
Excel.Visible[lcid] := True;
But in this case, you will gain some speed defining
Excel.ScreenUpdating[lcid] := False;
As I have already mentioned, accessing the cells is as in TStringGrid. Here is the whole process:
with ds do
begin
DisableControls;
//The first row is for the titles:
for i := 1 to ds.FieldCount do
if ds.Fields[i - 1].Visible then
begin
Worksheet.Cells.Item[1, i].Value :=
ds.Fields[i - 1].DisplayLabel;
Worksheet.Cells.Item[1, i].ColumnWidth :=
ds.Fields[i - 1].DisplayWidth;
end;
//Some special formatting for the whole title’s row:
Worksheet.Range['A1', 'A1'].EntireRow.Interior.Color := clGray;
Worksheet.Range['A1', 'A1'].Font.FontStyle := 'Bold';
L := 2;
FIRST;
while not (EOF) do
begin
for i := 1 to ds.FieldCount do
if ds.Fields[i - 1].Visible then
begin
//Some special conditions for specific fields; additional formatting
or checks could be added here
if GetLookUpTableName(ds.Fields[i - 1].FieldName, sTable) then
Worksheet.Cells.Item[L, i].Value :=
GetLookUpValue(sTable, ds.Fields[i - 1].Text)
else
Worksheet.Cells.Item[L, i].Value :=
ds.Fields[i - 1].Text;
end;
Inc(L);
NEXT;
end;
end;
Now turn on the screen updating and you will see the worksheet. It is formatted according to your preferences and can contain a large amount of data ( I have tested a table with 134 fields and several thousands records). But I do not recommend exporting data like this if you have a lot of records.
TEXT FILE MEDIATION
Excel 2000 workbook have a new method – OpenText – which loads and parses a text file as a new workbook with a single sheet that contains the parsed text-file data. But even in Excel 97, if you have the Tab character as a delimiter, your text will be recognized and parsed by the Open method in similar way.
It is faster than filling the worksheet cell by cell. Possible disadvantage is that the formatting has to be separated from the export. If you want to format any specific field, you should record and save its position during the file preparation. It is easy to process formatting after the worksheet is prepared if you have the coordinates of the field saved.
Next I use variants to access the workbook and the worksheet objects and the TExcelAplication component:
//After exporting the Dataset to a Tab-delimited text file and closing this file:
Excel.Connect;
lcid := GetUserDefaultLCID;
WbK := Excel.Workbooks.Open(tFileName, EmptyParam, EmptyParam,
EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam,
EmptyParam, EmptyParam, EmptyParam, EmptyParam, lcid);
ws := wbk.worksheets[1];
Excel.Visible[lcid] := True;
ws.activate;
THE POWERFULL CLIPBOARD
The Delphi huge string prompts another approach: replacing the text file with only one string, opening an empty worksheet and posting this string onto it. Excel wisely behaves similary and arranges the cells by itself according to the Tab and Enter delimiters. This approach is safer since the file routines are being avoided and it is even faster:
procedure TModule.PrintGrid3(ds: TDataSet; Header: string);
var
S: AnsiString;
VisCol, L, i: Integer;
sTable: string;
begin
with ds do
begin
DisableControls;
for i := 0 to ds.FieldCount - 1 do
if ds.Fields[i].Visible then
begin
S := S + ds.Fields[i].DisplayLabel;
if i <> ds.FieldCount - 1 then
S := S + #9;
Inc(VisCol);
end;
S := S + #13;
FIRST;
while not (EOF) do
begin
for i := 0 to ds.FieldCount - 1 do
if ds.Fields[i].Visible then
begin
S := S + ds.Fields[i].Text {+ #9};
if i <> ds.FieldCount - 1 then
S := S + #9;
Inc(L);
end;
S := S + #13;
NEXT;
end;
// Now copy the string :
Clipboard.SetTextBuf(PChar(S);
//Connect Excel:
Excel.Connect;
lcid := GetUserDefaultLCID;
Workbook.ConnectTo(Excel.Workbooks.Add(TOleEnum(xlWBATWorksheet), lcid));
Worksheet.ConnectTo(Workbook.Worksheets[1] as _Worksheet);
Worksheet.Name := 'MyData';
//Paste the string and clear the memory:
Worksheet.Cells.Item[1, 1].Select;
Worksheet.Paste;
Clipboard.Clear;
That is it.The result is the same: your data is on place. Next lines show how to generate a ready for printing report from it and to present the PrintPreview form on the screen of your client:
//Formating column widths without any calculations:
Worksheet.Columns.AutoFit;
//Column titles:
Worksheet.Range['A1', 'A1'].EntireRow.Interior.Color := clGray;
Worksheet.Range['A1', 'A1'].EntireRow.HorizontalAlignment := 1;
Worksheet.Range['A1', 'A1'].Font.FontStyle := 'Bold';
Worksheet.PageSetup.PrintGridlines := true;
//Header and footer:
Worksheet.PageSetup.CenterHeader := Header;
Worksheet.PageSetup.LeftFooter := ‘Some Text’;
Worksheet.PageSetup.FirstPageNumber := 1;
Excel.Visible[lcid] := True;
Worksheet.PrintOut(EmptyParam, EmptyParam, 1, 1);
Excel.ScreenUpdating[lcid] := True;
Workbook.Close(False);
end;
Of course, you could just print the report without showing the preview (see the PrintOut method parameters) or proceed in a different direction: sending the data by fax or e-mail or exporting it again, using now the Excel capacities for data processing. Word is also applicable for dataset export and printing (the TextToTable method) but Excel copes better with large datasets. It is worth experimenting with various solutions in order to reach the best result.
With every new version the Office applications are going to be more and more complex. New objects, methods and properties are being added and there are already so many different capacities that even the Office creators would perhaps find it hard hard to simply count them. It is a strenuous task for the common user to learn them all. But Microsoft Office is charged with much useful building material for the inventive developer and Automation is the key for taking advantage of it.
Related resources
Charlie Calvert: Delphi 4 Unleashed
MS Excel.how by Gary White, dBVIPS
Automating Microsoft Excel : Sources of information, Sample project, How do I and Common problems by Deborah Pate.
2008. március 29., szombat
Intercepting Windows messages in non-visual components
Problem/Question/Abstract:
Sometimes we need a non-windowed component (i.e. one that isn't derived from TWinControl) to receive Windows messages - but non-windowed component don't have window handles. For example suppose we are developing a non-visual component that registers our application as a clipboard viewer so the application can respond to changes in the clipboard. To get information about clipboard changes our component needs to receive messages from Windows.
Answer:
The Delphi library function AllocateHWnd is used to create a hidden window for us and the related DeallocateHWnd disposes of the window when we've finished with it.
The hidden window needs a window procedure. We can use a method of our component class to provide the window procedure. AllocateHWnd takes a reference to the method its parameter - it takes care of the problem of registering the method as a window procedure for us. In the method we handle the messages we are interested in and hand the rest off to Windows using the DefWindowProc API call.
The following code gives the skeleton of how to use AllocateHWnd. First, here's the class declaration from the interface section of code:
type
// Our class derived from TComponent
// (or another ancestor class)
TMyClass = class(TComponent)
private
FHWnd: HWND;
// field to store the window handle
{...}
protected
procedure WndMethod(var Msg: TMessage); virtual;
// the window proc - called by Windows to handle
// the given message
{...}
public
constructor Create(AOwner: TComponent); override;
// create window proc here
destructor Destroy; override;
// free window proc here
{...}
end;
And here's the implementation details:
TMyClass.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
{... }
// Create the window
FHWnd := AllocateHWnd(WndMethod);
{ ...}
end;
TMyClass.Destroy;
begin
{...}
// Destroy the window
DeallocateHWnd(FHWnd);
{...}
inherited Destroy;
end;
TMyClass.WndMethod(var Msg: TMessage);
var
Handled: Boolean;
begin
// Assume we handle message
Handled := True;
case Msg.Msg of
WM_SOMETHING: DoSomething;
// Code to handle a message
WM_SOMETHINGELSE: DoSomethingElse;
// Code to handle another message
{...}
else
// We didn't handle message
Handled := False;
end;
if Handled then
// We handled message - record in message result
Msg.Result := 0
else
// We didn't handle message
// pass to DefWindowProc and record result
Msg.Result := DefWindowProc(FHWnd, Msg.Msg,
Msg.WParam, Msg.LParam);
end;
Of course, we could just use the Windows API to create a window the hard way and provide a windows procedure. But it is more difficult to use a method (rather than a simple procedure) as a window procedure if we do it this way. The clever features about AllocateHWnd are that (a) it creates the hidden window for us and (b) it allows us to use a method, rather than a simple procedure as the window procedure -- and a method is more useful since it has access to the class's private data.
Component Download: http://www.delphidabbler.com/download.php?file=pjcbview.zip
2008. március 28., péntek
Find a constant dynamically
Problem/Question/Abstract:
In my application, the user has a form where he is selecting from a list of options. That list of options corresponds to contant values. Is there a way to find the value of a constant when all you have is a string containing its name?
Example:
const
TEST = 5;
DELIVERED = 10
{ ... }
sChoice := listboxChoice.Text // -possible values are TEST and DELIVERED
iChoice = {missing method}(sChoice)
iChoice would be assigned the value of the constant of the same name as the user's selection. No, I can't change the declarations to be enumerated types or anything else. They have to be constants. I've seen examples of this sort of thing done for Enumerated types, objects and so on using RTTI. But I can't find an example of constants, and I can't figure it out.
Answer:
Solve 1:
If enumerations are okay (I don't see how you could use names of constants), try this to get mapping of enumeration from string to integer:
{ ... }
type
TConstValues = (cvTest, cvDelivered);
var
values = array[TConstValues] of integer = (5, 10);
strings = array[TConstValues] of string = ('TEST', 'DELIVERED');
{ ... }
function GetConstValue(s: string): integer;
var
t: TConstValues;
begin
result := -1;
for t := low(TConstValues) to high(TConstvalues) do
if strings[t] = s then
begin
result := values[t];
break;
end;
end;
Solve 2:
This is a modification of Solve 1:
{ ... }
const
TCVals: array[TConstValues] of integer = (-1, 5, 10);
TCStrs: array[TConstValues] of string = ('UNKNOWN', 'TEST', 'DELIVERED');
{ ... }
function GetConstValue(s: string): integer;
var
t: TConstValues;
begin
t := high(TConstvalues);
while (t > low(TConstValues)) and (CompareText(TCStrs[t], s) <> 0) do
dec(t);
Result := TCVals[t];
end;
2008. március 27., csütörtök
Hidden features of the Delphi IDE
Problem/Question/Abstract:
Hidden features of the Delphi IDE
Answer:
Some undocumented registry settings of Delphi 5 (which -slightly adapted- might also work with Delphi 4 and below) modify the behavior of the Delphi component palette in a manner you may like!
Most values are stored as strings, and boolean values are represented as "1" for true and "0" for false. All values are stored in
HKEY_CURRENT_USER
As always, use of this information is at your own risk... ;-)
Software\Borland\Delphi\5.0\Extras\AutoPaletteSelect
will cause a tab on the component palette to be automatically selected when the mouse is hovering over it. If the mouse is in the top two- thirds (2/3) of the tab, the palette for that tab will automatically be displayed.
Software\Borland\Delphi\5.0\Extras\AutoPaletteScroll
will make you scroll left and right automatically whenever the mouse is positioned over the relevant arrow.
Software\Borland\Delphi\5.0\Editor\Options\NoCtrlAltKeys
Disables menu item Ctrl+Alt key sequences for international keyboards
Software\Borland\Delphi\5.0\Form Design\AlwaysEnableMiddleEast
Forces Right-to-Left text in the form designer (?)
Software\Borland\Delphi\5.0\Extras\FontNamePropertyDisplayFontNames
Display the fonts in the object inspector dropdown in the font's actual style (slow with many fonts installed). See also DsgnIntf.FontNamePropertyDisplayFontNames in D5.
Software\Borland\Delphi\5.0\Compiling\ShowCodeInsiteErrors
Show compilation errors found by CodeInsite in the message view window
Software\Borland\Delphi\5.0\Globals\PropValueColor
Fill in with a string like "clGreen" to change the color of the right half (properties) of the Object Inspector.
Software\Borland\Delphi\5.0\Disabled Packages
This is the place you put Delphi Direct :)
Software\Borland\Delphi\5.0\Globals\TwoDigitYearCenturyWindow
Default value for TwoDigitYearCenturyWindow (see the help file)
Software\Borland\Delphi\5.0\Component Templates\CCLibDir
Alternative component templates directory (shared/network)
Software\Borland\Delphi\5.0\FormDesign\DefaultFont="Arial,8" [D4] or "Arial,8,Bold" [D5]
The default for for new forms (you might prefer using the repository's default form checkbox instead)
Software\Borland\Delphi\5.0\Wizards
Alternate key to store Expert/Wizard DLLs to load at startup
Software\Borland\Delphi\5.0\Debugging\DontPromptForJITDebugger
Don't ask to change the current JIT debugger (?)
Software\Borland\Delphi\5.0\Version Control\VCSManager
The DLL used for the version control interface in the IDE.
Software\Borland\Delphi\5.0\Globals\PrivateDir
A way to specify an alternative directory for the location for the Delphi configuration files when running the application from a network drive or the CD-ROM.
Software\Borland\Delphi\5.0\Main Window\Palette Visible
Software\Borland\Delphi\5.0\Main Window\Speedbar Visible
Software\Borland\Delphi\5.0\Main Window\Palette Hints
Software\Borland\Delphi\5.0\Main Window\Speedbar Hints
Software\Borland\Delphi\5.0\Main Window\Split Position
These seem to have no effect at runtime, but are read by the IDE. The actually used values come from
HKEY_CURRENT_USER\Software\Borland\Delphi\5.0\Toolbars
Software\Borland\Delphi\5.0\ProjectManager\Dockable
Software\Borland\Delphi\5.0\PropertyInspector\Dockable
Software\Borland\Delphi\5.0\CallStackWindow\Dockable
Software\Borland\Delphi\5.0\ModuleWindow\Dockable
Read but unused settings. Used values come from DSK files.
There are lots of other interesting registry keys that aren't modifiable in the IDE, but they all have values written by default, so you can find and play with them much easier.
2008. március 26., szerda
Form as Application
Problem/Question/Abstract:
How i can create a form and this form stay in another icon in task bar ? (Looks like a new aplication).
Answer:
In private clause:
type
TForm1 = class(TForm)
private
{ Private declarations }
procedure CreateParams(var Params: TCreateParams); override;
And, in the implementation:
procedure TForm1.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
with params do
ExStyle := ExStyle or WS_EX_APPWINDOW;
end;
2008. március 25., kedd
Invert colors in a TImage
Problem/Question/Abstract:
Is there an easy way to invert the colors in a TImage? I have a bitmap that is a black background with white text and I want it to be a white background with black text.
Answer:
Solve 1:
Ok, first we need a little theory of how an image is represented in the screen, computers use a color model in which images are represented in pixels (picture elements), each pixel can be represented with a pixel depth, in other words, the information of each pixel can be stored in diferent number of bits.
For example images with a pixel depth of 8 bits can store a maximum of 256 colors since each bit can have one of two values (0 or 1), we have 2x2x2x2x2x2x2x2 = 256.
Nowadays we have images of 24 and even 32 pixels depth, I will cover how to obtain invert the color of only this kind of images.
The RGB color model (where R=Red, G=Green, and B=Blue) threats an image of 24 pixels depth, as it is divided in 3 color chanels, where each chanel consits of 8 bits, and once again we have up to 256 posible values for each chanel, if we add the three chanels we have the final representation of the image.
Ok, enough theory, we can acomplish that with the following procedure:
procedure InvertImage(const AnImage: TImage);
var
BytesPorScan: integer;
vi_width, vi_height: integer;
p: pByteArray;
begin
//This only works with images of 24 or 32 bits per pixel
if not (AnImage.Picture.Bitmap.PixelFormat in [pf24Bit, pf32Bit]) then
raise exception.create('Error, Format File not soported!');
try
BytesPorScan := Abs(Integer(AnImage.Picture.Bitmap.ScanLine[1]) -
Integer(AnImage.Picture.Bitmap.ScanLine[0]));
except
raise exception.create('Error');
end;
//Invert the RGB for each pixel
for vi_height := 0 to AnImage.Picture.Bitmap.Height - 1 do
begin
P := AnImage.Picture.Bitmap.ScanLine[vi_height];
for vi_width := 0 to BytesPorScan - 1 do
P^[vi_width] := 255 - P^[vi_width];
end;
AnImage.Refresh;
end;
the important part is the for loop, since the values for each color chanel can vary from 0 to 255, whe only have to substract the actual value of the pixel (P^[vi_width) from 255 to obtain the inverse color, and assign this new value to the pixel.
Solve 2:
begin
Image1.Canvas.CopyMode := cmDstInvert;
Image1.Canvas.CopyRect(Image1.ClientRect, Image1.Canvas, Image1.ClientRect);
imgZoom.Canvas.CopyMode := cmSrcCopy;
end
Solve 3:
var
R: TRect;
begin
{ ... }
with Image1.Picture.Bitmap do
begin
R := Rect(0, 0, Width, Height);
InvertRect(Canvas.Handle, R);
end;
Image1.Invalidate;
{ ... }
2008. március 24., hétfő
Restrict TEdit input to floating point numbers and a defined number of decimal places
Problem/Question/Abstract:
I would like a TEdit box on my form that only accepts keys to enter a floating point number that has N number of decimal places. What is the best way to do this?
Answer:
Derive a new component from TEdit and override its KeyPress method. That is fed characters before the control has inserted them into the text. You can examine the character, reject it outright if it would not be valid for a floating point number. If it would be valid you have to examine the content of the edit, the values of SelStart and SelCount and figure out how the content would look like if you let the key pass throuogh. Test that new string against what you can accept, if it does not match you reject the key. The following OnKeyPress handler for a normal tedit control shows the logic. It should be integrated into a new component which also would have a property to set the number of allowable decimal digits.
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
const
MAXNUMBEROFDIGITS = 2;
var
S: string;
val: Double;
n: Integer;
begin
if key <> #8 then {Always let backspace through}
if key in ['0'..'9', '-', DecimalSeparator] then
begin
{key is a candidate}
with Sender as TEdit do
begin
S := Text;
if SelLength > 0 then {key will replace selection}
Delete(S, SelStart + 1, SelLength);
Insert(key, S, SelStart + 1);
{S now has string as it would look after key is processed. Check if it is a valid floating point number.}
try
val := StrToFloat(S);
{OK, it computes. Find the decimal point and count the digits after it.}
n := Pos(decimalSeparator, S);
if n > 0 then
if (Length(S) - n) > MAXNUMBEROFDIGITS then
{too many digits, reject key}
key := #0;
except
{nope, reject key}
key := #0;
end;
end;
end
else {key not acceptible}
key := #0;
end;
2008. március 23., vasárnap
Importing XML DOM Parser in Delphi
Problem/Question/Abstract:
How to import XML DOM Parser component in Delphi?
Answer:
One of the new features of Delphi 5 is that we can install COM servers as components in Delphi IDE. There are already some COM servers installed on the Servers Palette like Microsoft word, Excel etc., Other than that, the user can install other COM servers through the Project | Import Type library. This will create a wrapper class for that component. We can use that wrapper class to build a package in Delphi and install as component in the IDE. This is really a great and cool feature in Delphi 5.
This option will be very helpful when you want to use a COM component written in other languages. You just need to import them through this Import Type Library option. You can access the methods/properties of that COM component from Delphi 5.
I used msxml.dll(Version 2.0) to install the XML parsing components in the IDE through the Import Type Library option.
Steps to import XML DOM parser component:
Go to Project|Import Type Library(See Fig. 1. Below)
Select Microsoft XML, version 2.0(Version 2.0)
Then choose Create Unit
This will create a wrapper class for that parser in a pascal file
You can put that pascal file in a package and install it.
You will be getting a set of components installed on the ActiveX palette in Delphi
Among them will be the component called DOMDocument
Now you can use the methods of that component to parse the XML. There are some more components like OMFreeThreadedDocument,XMLHTTPRequest,XMLDSOControl and XMLDocument.
There are two ways to load the XML into that component to parse.
1. Loading the XML as a string:
You can use the “loadXML” method to load a XML string
For eg. DOMDocument.loadXML(‘XML string’);
2. Loading the XML as a file:
You can use the “load” method to load a XML file.
For eg. DOMDocument.load(‘Path of the XML file’)
Once you load either the XML string or the XML file into that component, the XML will be parsed and if there are any errors during parsing, then those errors will be intimated to the user by the way of exception.
You can check the place where you get the parsing error and also the reason for that.
Finding the place where the parsing error occurred:
DOMDocument.parseError.srcText will give you the exact line in the XML where the error occurred.
The above two are really helpful in diagnosing the parsing errors.
Getting the reason for the parsing error:
DOMDocument.parseError.reason will tell you the reason for that parsing error.
If there are no errors reported by the parser, then we can get the parsed data through the methods like getElementsByTagName, Get_nodeName, Get_nodeValue etc.,
The DTD file used in that XML should be in the search path of the application or should be in the path where the exe resides. Only then the application will be able to see DTD info used in that XML file or string and parse correctly.
Important:
So when we deploy an application in a fresh machine, we should not forget to include this DTD file in the same path as the exe is.
Please make sure all the XML string/XML file you are sending to that component follow the DTD mentioned in that DTD file. Even a small spelling mistake or an extra letter will cause an exception.
2008. március 22., szombat
Extracting Version Information
Problem/Question/Abstract:
How can I display some fields from my application's version information?
Answer:
I provide you the object that could be used for extracting version information from executables and libraries.
unit siverinfo;
interface
uses Windows, Classes, SysUtils;
type
TVersionInfo = class(TObject)
private
FData: Pointer;
FSize: Cardinal;
FCompanyName: string;
FFileDescription: string;
FFileVersion: string;
FInternalName: string;
FLegalCopyright: string;
FLegalTrademarks: string;
FOriginalFilename: string;
FProductName: string;
FProductVersion: string;
FComments: string;
public
constructor Create(FileName: string);
destructor Destroy; override;
property CompanyName: string read FCompanyName;
property FileDescription: string read FFileDescription;
property FileVersion: string read FFileVersion;
property InternalName: string read FInternalName;
property LegalCopyright: string read FLegalCopyright;
property LegalTrademarks: string read FLegalTrademarks;
property OriginalFilename: string read FOriginalFilename;
property ProductName: string read FProductName;
property ProductVersion: string read FProductVersion;
property Comments: string read FComments;
end;
implementation
{ TVersionInfo }
constructor TVersionInfo.Create(FileName: string);
var
sz, lpHandle, tbl: Cardinal;
lpBuffer: Pointer;
str: PChar;
strtbl: string;
int: PInteger;
hiW, loW: Word;
begin
inherited Create;
FSize := GetFileVersionInfoSize(PChar(FileName), lpHandle);
FData := AllocMem(FSize);
GetFileVersionInfo(PChar(FileName), lpHandle, FSize, FData);
VerQueryValue(FData, '\\VarFileInfo\Translation', lpBuffer, sz);
int := lpBuffer;
hiW := HiWord(int^);
loW := LoWord(int^);
tbl := (loW shl 16) or hiW;
strtbl := Format('%x', [tbl]);
if Length(strtbl) < 8 then
strtbl := '0' + strtbl;
VerQueryValue(FData, PChar('\\StringFileInfo\' + strtbl + '\CompanyName'), lpBuffer,
sz);
str := lpBuffer;
FCompanyName := str;
VerQueryValue(FData, PChar('\\StringFileInfo\' + strtbl + '\FileDescription'),
lpBuffer, sz);
str := lpBuffer;
FFileDescription := str;
VerQueryValue(FData, PChar('\\StringFileInfo\' + strtbl + '\FileVersion'), lpBuffer,
sz);
str := lpBuffer;
FFileVersion := str;
VerQueryValue(FData, PChar('\\StringFileInfo\' + strtbl + '\InternalName'),
lpBuffer, sz);
str := lpBuffer;
FInternalName := str;
VerQueryValue(FData, PChar('\\StringFileInfo\' + strtbl + '\LegalCopyright'),
lpBuffer, sz);
str := lpBuffer;
FLegalCopyright := str;
VerQueryValue(FData, PChar('\\StringFileInfo\' + strtbl + '\LegalTrademarks'),
lpBuffer, sz);
str := lpBuffer;
FLegalTrademarks := str;
VerQueryValue(FData, PChar('\\StringFileInfo\' + strtbl + '\OriginalFilename'),
lpBuffer, sz);
str := lpBuffer;
FOriginalFilename := str;
VerQueryValue(FData, PChar('\\StringFileInfo\' + strtbl + '\ProductName'), lpBuffer,
sz);
str := lpBuffer;
FProductName := str;
VerQueryValue(FData, PChar('\\StringFileInfo\' + strtbl + '\ProductVersion'),
lpBuffer, sz);
str := lpBuffer;
FProductVersion := str;
VerQueryValue(FData, PChar('\\StringFileInfo\' + strtbl + '\Comments'), lpBuffer,
sz);
str := lpBuffer;
FComments := str;
end;
destructor TVersionInfo.Destroy;
begin
FreeMem(FData);
inherited;
end;
end.
2008. március 21., péntek
Rewrite the last line of text in a text file
Problem/Question/Abstract:
How to rewrite the last line of text in a text file
Answer:
procedure RewriteLastTextLine(AFileName: string; ANewTextLine: string);
const
BUFFER_SIZE = 1024; {change this number for different sized buffer}
CRLF = #13#10;
var
fs: TFileStream;
buf: PChar;
iStartWritePos: Int64;
function AssignPos: Boolean;
var
i: Integer;
begin
for i := BUFFER_SIZE - 1 downto 0 do
if (buf[i] = #13) then
begin
iStartWritePos := (iStartWritePos - (BUFFER_SIZE - i));
Result := True;
Exit;
end;
Result := False;
end;
procedure ReadABuffer;
begin
fs.Position := fs.Position - BUFFER_SIZE;
fs.Read(buf^, BUFFER_SIZE);
fs.Position := fs.Position - BUFFER_SIZE;
end;
begin
fs := TFileStream.Create(AFileName, fmOpenReadWrite or fmShareDenyWrite);
try
GetMem(buf, BUFFER_SIZE);
FillMemory(buf, BUFFER_SIZE, 0);
fs.Position := fs.Size;
iStartWritePos := fs.Position;
repeat
ReadABuffer
until
AssignPos;
fs.Position := iStartWritePos;
fs.Write(CRLF, Length(CRLF));
fs.Write(ANewTextLine[1], Length(ANewTextLine));
finally
FreeMem(buf, BUFFER_SIZE);
fs.Free;
end;
end;
2008. március 20., csütörtök
Print a TRichEdit upside down
Problem/Question/Abstract:
How to print a TRichEdit upside down
Answer:
Below are the 4 base orientations, but TXForm (in windows.pas) gives you the ability to turn the world to any degree. World transformations are interesting in that they raise the prospect of working in code with portrait objects and simply turning the world, rather than turning the objects individually to conform to the world - which I have been doing up until now.
Note: This does not work for Win9x.
{ ... }
type
TWorldOrientation = (woPortrait, woLandscape, woInversePortrait,
woInverseLandscape);
function GetWorldOrientation(APageRect: TRect; AOrientation: TWorldOrientation):
TXForm;
begin
case AOrientation of
woPortrait:
begin
Result.eM11 := 0;
Result.eM12 := 0;
Result.eM21 := 0;
Result.eM22 := 0;
Result.eDX := APageRect.Left;
Result.eDY := APageRect.Top;
end;
woLandscape:
begin
Result.eM11 := 0;
Result.eM12 := -1;
Result.eM21 := 1;
Result.eM22 := 0;
Result.eDX := APageRect.Left;
Result.eDY := APageRect.Bottom;
end;
woInversePortrait:
begin
Result.eM11 := -1;
Result.eM12 := 0;
Result.eM21 := 0;
Result.eM22 := -1;
Result.eDX := APageRect.Right;
Result.eDY := APageRect.Bottom;
end;
woInverseLandscape:
begin
Result.eM11 := 0;
Result.eM12 := 1;
Result.eM21 := -1;
Result.eM22 := 0;
Result.eDX := APageRect.Right;
Result.eDY := APageRect.Top;
end;
end;
end;
function PrintText(ACanvas: TCanvas; APageRect, APrintRect: TRect; AText: string;
ATextFlags:
Integer; AOrientation: TWorldOrientation): Boolean;
var
SaveGM: Integer;
SaveXF: TXForm; // unit Windows.pas
begin
{save graphics mode}
SaveGM := Windows.GetGraphicsMode(ACanvas.Handle);
{can we do it}
Result := Windows.SetGraphicsMode(aCanvas.Handle, GM_ADVANCED) <> 0;
if Result then
begin
{save transform}
Windows.GetWorldTransform(ACanvas.Handle, SaveXF);
// set orientation
Windows.SetWorldTransform(ACanvas.Handle, GetWorldOrientation(APageRect,
AOrientation));
{move text to page}
Windows.DrawText(ACanvas.Handle, PChar(AText), -1, APrintRect, ATextFlags);
{restore transform}
Windows.SetWorldTransform(ACanvas.Handle, SaveXF);
{restore graphics mode}
Windows.SetGraphicsMode(aCanvas.Handle, SaveGM);
end;
end;
function PrintRichText(ACanvas: TCanvas; APageRect, APrintRect: TRect; ARichEdit:
TRichEdit;
APixelsPerInchX, APixelsPerInchY: Integer; AOrientation: TWorldOrientation):
Boolean;
const
RICH_TWIPS = 1440;
var
SaveGM: Integer;
SaveXF: TXForm; {unit Windows.pas}
FmtRange: TFormatRange; {unit RichEdit.pas}
begin
{save graphics mode}
SaveGM := Windows.GetGraphicsMode(ACanvas.Handle);
{can we do it}
Result := Windows.SetGraphicsMode(aCanvas.Handle, GM_ADVANCED) <> 0;
if Result then
begin
{save transform}
Windows.GetWorldTransform(ACanvas.Handle, SaveXF);
{set orientation}
Windows.SetWorldTransform(ACanvas.Handle, GetWorldOrientation(APageRect,
AOrientation));
{adjust for twips}
APrintRect.Left := APrintRect.Left * RICH_TWIPS div APixelsPerInchX;
APrintRect.Top := APrintRect.Top * RICH_TWIPS div APixelsPerInchY;
APrintRect.Right := APrintRect.Right * RICH_TWIPS div APixelsPerInchX;
APrintRect.Bottom := APrintRect.Bottom * RICH_TWIPS div APixelsPerInchY;
{move rich text to page}
System.FillChar(FmtRange, SizeOf(FmtRange), 0);
FmtRange.Hdc := ACanvas.Handle;
FmtRange.HdcTarget := ACanvas.Handle;
FmtRange.Rc := APrintRect;
FmtRange.ChrG.CpMin := 0;
FmtRange.ChrG.CpMax := Length(ARichEdit.Text);
ARichEdit.Perform(EM_FORMATRANGE, 1, LongInt(@FmtRange));
ARichEdit.Perform(EM_FORMATRANGE, 0, 0);
{restore transform}
Windows.SetWorldTransform(ACanvas.Handle, SaveXF);
{restore graphics mode}
Windows.SetGraphicsMode(aCanvas.Handle, SaveGM);
end;
end;
Examples:
procedure TForm1.FormCreate(Sender: TObject);
begin
{Apparently you need to initialise before using the first time otherwise the
canvas doesn't appear to paint properly}
Windows.SetGraphicsMode(Self.Canvas.Handle, GM_ADVANCED);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
R: TRect;
begin
R := Rect(20, 20, 200, 200);
PrintText(Self.Canvas, Self.ClientRect, R, 'I am portrait', 0, woPortrait);
PrintText(Self.Canvas, Self.ClientRect, R, 'I am landscape', 0, woLandscape);
PrintText(Self.Canvas, Self.ClientRect, R, 'We are inverse portrait' + #13#10 +
'As are we', 0, woInversePortrait);
PrintText(Self.Canvas, Self.ClientRect, R, 'We are inverse landscape.' + #13#10 +
'Us to', DT_RIGHT, woInverseLandscape);
end;
procedure TForm1.Button2Click(Sender: TObject);
var
R: TRect;
begin
if OpenDialog1.Execute then
begin
RichEdit1.Lines.LoadFromFile(OPenDialog1.FileName);
R := Rect(10, 10, 200, 300);
PrintRichText(Self.Canvas, Self.ClientRect, R, RichEdit1, Screen.PixelsPerInch,
Screen.PixelsPerInch, woPortrait);
PrintRichText(Self.Canvas, Self.ClientRect, R, RichEdit1, Screen.PixelsPerInch,
Screen.PixelsPerInch, woLandscape);
PrintRichText(Self.Canvas, Self.ClientRect, R, RichEdit1, Screen.PixelsPerInch,
Screen.PixelsPerInch, woInverseLandscape);
PrintRichText(Self.Canvas, Self.ClientRect, R, RichEdit1, Screen.PixelsPerInch,
Screen.PixelsPerInch, woInversePortrait);
end;
end;
2008. március 19., szerda
Adding custom registry information on registration
Problem/Question/Abstract:
How to add custom information to the registry at the time an ActiveX control is registered.
Answer:
There are a number of times an ActiveX control writer will wont to insert custom information into the registry when a control is registered, and then remove the information when the control is unregistered.
For example, you often need to insert custom information into the registry if you are writing a plugin/addin for another program (Microsoft Office products all require this), or you may want to include some extra information about your control that is not put there automatically (see the article on making insertable controls). With Visual Basic you have to make a .Reg file to do this. Luckily, with Delphi, we can do better.
To do this you need to override the default initialization of your ActiveX control. To do this you need to create a custom class that inherits from either TActiveFormFactory or TActiveXControlFactory depending if you are in an ActiveForm project or an ActiveX Control project. (Note: TActiveFormFactory inherits from TActiveXControlFactory).
So, we create a new class call TMyFactory. There is one procedure we want to override call UpdateRegistry. It has one parameter call “Register”. If this is true your control is being registered, if it is false your control is being unregistered.
My sample class is shown below.
uses
AxCtrls, Registry;
type
TMyFactory = class(TActiveFormFactory)
private
public
procedure UpdateRegistry(Register: Boolean); override;
end;
implementation
{ TMyActiveFormFactory }
//---------------------------------------------------------
// UdateRegistry
// This procedure is called anytime you're ActiveX control
// registered or unregistered.
// Params: Register; if True you are being registered
// if False you are being unregistered
//---------------------------------------------------------
procedure TMyFactory.UpdateRegistry(Register: Boolean);
var
oReg: TRegistry;
begin
inherited;
oReg := TRegistry.Create;
try
if Register then
begin
// add extra registration entries here
end
else
begin
// remove extra registration entries here
end;
finally
oReg.Free;
end
end;
When that is all done, go to the initialization section of your ActiveX control and change it to the following (the example is for an ActiveForm) and you are done.
initialization
//TActiveFormFactory.Create( // old class factory
TMyFactory.Create(// your new class factory
ComServer,
TActiveFormControl,
TActiveFormX,
Class_ActiveFormX,
1,
'',
OLEMISC_SIMPLEFRAME or OLEMISC_ACTSLIKELABEL,
tmApartment);
Things to remember:
If you need your control’s ProgID or ClassID that information is already there, passed in with the constructor and saved in FClassID. To get the ProgID use the function ClassIDToProgID found in ComObj.
Because this code is called during registration debugging is possible but very difficult. Essentially you have to recompile the VCL to use debug DCU’s. Even then no guarantees with the UpdateRegistry function.
There are other functions and procedures that you can overwrite here. Some commonly overwritten procedures include: Create, GetLicenseString, HasMachineLicense, ValidateUserLicense, and occasionally GetProgID.
Just because you put the information into the registry does not necessarily mean you want to take it out. One case for this behavior is when you want a control to work for a while then stop (so a user is forced to buy the control).
You aren’t restricted to the registry; many programs are now using XML for loading plugins/addins instead of the Registry.
2008. március 18., kedd
Touch a file with a specified date/time
Problem/Question/Abstract:
How do I change the date & time of a file specified as a string?
Answer:
Often A file's time is set to represent a version number. For example the datetime may be January 27, 2000 1:03:00AM to represent version 1 patch 3.
This unit presents a procedure which takes two parameters, A file path/name specified as a string and a DateTime.
The specified file's date & time will be changed to match the DateTime specified.
unit Fileutil;
interface
uses System, SysUtils;
{To Change the Date/Time of a file}
procedure TouchFile(FileName: string; Date: TDateTime);
implementation
procedure TouchFile(FileName: string; Date: TDateTime);
var
TheFile: file;
begin
AssignFile(TheFile, FileName);
Reset(TheFile);
FileSetDate(TFileRec(TheFile).Handle,
DateTimeToFileDate(Date));
Close(TheFile);
end;
end.
2008. március 17., hétfő
Enumerating workgroups on your LAN
Problem/Question/Abstract:
Enumerating workgroups on your LAN.
Answer:
{ This code is a copy-paste from a working application.
}
var
WorkgroupCount: Integer;
Workgroup: array[1..500] of string[25];
procedure FindAllWorkgroups;
var
EnumHandle: THandle;
EntireNetwork: TNetResource;
Buf: array[1..500] of TNetResource;
BufSize: Integer; // or DWORD;
Entries: Integer; // or DWORD;
begin
FillChar(EntireNetwork, SizeOf(EntireNetwork), 0);
with EntireNetwork do
begin
dwScope := 2;
dwDisplayType := 6;
dwUsage := 2;
end;
WNetOpenEnum(RESOURCE_GLOBALNET,
RESOURCETYPE_ANY,
0,
@EntireNetwork,
EnumHandle);
WorkgroupCount := 0;
repeat
Entries := 1;
BufSize := SizeOf(Buf);
WNetEnumResource(EnumHandle,
Entries,
@Buf,
BufSize);
if Entries = 1 then
begin
Inc(WorkgroupCount);
Workgroup[WorkgroupCount] := StrPas(Buf[1].lpRemoteName);
end;
until (Entries <> 1);
WNetCloseEnum(EnumHandle);
end;
2008. március 16., vasárnap
Copy a WideString to the clipboard
Problem/Question/Abstract:
I must copy a Unicode string to clipboard. How?
Answer:
Use
Clipboard.SetAsHandle(CF_UNICODETEXT, hUnicodeHandle);
Here's the small code snippet:
{ ... }
mem := GlobalAlloc(GMEM_MOVEABLE + GMEM_DDESHARE, SelLen);
ptr := GlobalLock(mem);
Move(PWideChar(SelText)^, ptr^, SelLen);
GlobalUnlock(mem);
Clipboard.SetAsHandle(CF_UNICODETEXT, mem);
{ ... }
I must copy a Unicode string to clipboard. How?
Answer:
Use
Clipboard.SetAsHandle(CF_UNICODETEXT, hUnicodeHandle);
Here's the small code snippet:
{ ... }
mem := GlobalAlloc(GMEM_MOVEABLE + GMEM_DDESHARE, SelLen);
ptr := GlobalLock(mem);
Move(PWideChar(SelText)^, ptr^, SelLen);
GlobalUnlock(mem);
Clipboard.SetAsHandle(CF_UNICODETEXT, mem);
{ ... }
2008. március 15., szombat
Getting Rid Of the Annoying SQL Wait Cursor
Problem/Question/Abstract:
How can I Get Rid Of the Annoying SQL Wait Cursor?
Answer:
// Torry's Delphi Tips
// Author Garret Bryl
// Listed 20.02.2003
{
Simply place this one line of code in the OnCreate event of
the form that is showing the annoying SQL Wait cursor...
}
procedure TForm1.FormCreate(Sender: TObject);
begin
Screen.Cursors[crSQLWait] := Screen.Cursors[crHourGlass];
// or whatever cursor you would like to replace the SQL hourglass with
end;
How can I Get Rid Of the Annoying SQL Wait Cursor?
Answer:
// Torry's Delphi Tips
// Author Garret Bryl
// Listed 20.02.2003
{
Simply place this one line of code in the OnCreate event of
the form that is showing the annoying SQL Wait cursor...
}
procedure TForm1.FormCreate(Sender: TObject);
begin
Screen.Cursors[crSQLWait] := Screen.Cursors[crHourGlass];
// or whatever cursor you would like to replace the SQL hourglass with
end;
2008. március 14., péntek
Create a ScanLine implementation of Stretchblt
Problem/Question/Abstract:
How to create a ScanLine implementation of Stretchblt
Answer:
I'm using this routine for animated zooms, so I took special care to keep the stretch centered. In this scenario the simple stretch makes sense and improves performance. For thumbnailing, be aware that when you make a thumbnail from a bmp file from disk, then most of the time is spent on file I/O, the resampling time compared to that is peanuts, same goes for a jpeg, only for those the decoding is what takes long.
unit DeleteScans;
interface
uses
Windows, Graphics;
procedure DeleteScansRect(Src, Dest: TBitmap; rs, rd: TRect);
{ScanLine implementation of Stretchblt/Delete_Scans. About twice as fast.
Stretches Src to Dest, rs is source rect, rd is dest. rect. The stretch is centered,
i.e the center of rs is mapped to the center of rd. Src, Dest are assumed to be bottom up}
implementation
uses
Classes, Math;
type
TRGBArray = array[0..64000] of TRGBTriple;
PRGBArray = ^TRGBArray;
TQuadArray = array[0..64000] of TRGBQuad;
PQuadArray = ^TQuadArray;
procedure DeleteScansRect(Src, Dest: TBitmap; rs, rd: TRect);
var
xsteps, ysteps: array of Integer;
intscale: Integer;
i, x, y, x1, x2, bitspp, bytespp: Integer;
ts, td: PByte;
bs, bd, WS, hs, w, h: Integer;
Rows, rowd: PByte;
j, c: Integer;
pf: TPixelFormat;
xshift, yshift: Integer;
begin
WS := rs.Right - rs.Left;
hs := rs.Bottom - rs.Top;
w := rd.Right - rd.Left;
h := rd.Bottom - rd.Top;
pf := Src.PixelFormat;
if (pf <> pf32Bit) and (pf <> pf24bit) then
begin
pf := pf24bit;
Src.PixelFormat := pf;
end;
Dest.PixelFormat := pf;
if not (((w <= WS) and (h <= hs)) or ((w >= WS) and (h >= hs))) then
{We do not handle a mix of up-and downscaling, using threadsafe StretchBlt instead}
begin
Src.Canvas.Lock;
Dest.Canvas.Lock;
try
SetStretchBltMode(Dest.Canvas.Handle, STRETCH_DELETESCANS);
StretchBlt(Dest.Canvas.Handle, rd.Left, rd.Top, w, h, Src.Canvas.Handle,
rs.Left, rs.Top, WS, hs, SRCCopy);
finally
Dest.Canvas.Unlock;
Src.Canvas.Unlock;
end;
exit;
end;
if pf = pf24bit then
begin
bitspp := 24;
bytespp := 3;
end
else
begin
bitspp := 32;
bytespp := 4;
end;
bs := (Src.Width * bitspp + 31) and not 31;
bs := bs div 8; {BytesPerScanline Source}
bd := (Dest.Width * bitspp + 31) and not 31;
bd := bd div 8; {BytesPerScanline Dest}
if w < WS then {downsample}
begin
{first make arrays of the skipsteps}
SetLength(xsteps, w);
SetLength(ysteps, h);
intscale := round(WS / w * $10000);
x1 := 0;
x2 := (intscale + $7FFF) shr 16;
c := 0;
for i := 0 to w - 1 do
begin
xsteps[i] := (x2 - x1) * bytespp;
x1 := x2;
x2 := ((i + 2) * intscale + $7FFF) shr 16;
if i = w - 2 then
c := x1;
end;
xshift := min(max((WS - c) div 2, -rs.Left), Src.Width - rs.Right);
intscale := round(hs / h * $10000);
x1 := 0;
x2 := (intscale + $7FFF) shr 16;
c := 0;
for i := 0 to h - 1 do
begin
ysteps[i] := (x2 - x1) * bs;
x1 := x2;
x2 := ((i + 2) * intscale + $7FFF) shr 16;
if i = h - 2 then
c := x1;
end;
yshift := min(max((hs - c) div 2, -rs.Top), Src.Height - rs.Bottom);
if pf = pf24bit then
begin
Rows := @PRGBArray(Src.Scanline[rs.Top + yshift])^[rs.Left + xshift];
rowd := @PRGBArray(Dest.Scanline[rd.Top])^[rd.Left];
for y := 0 to h - 1 do
begin
ts := Rows;
td := rowd;
for x := 0 to w - 1 do
begin
pRGBTriple(td)^ := pRGBTriple(ts)^;
inc(td, bytespp);
inc(ts, xsteps[x]);
end;
Dec(rowd, bd);
Dec(Rows, ysteps[y]);
end;
end
else
begin
Rows := @PQuadArray(Src.Scanline[rs.Top + yshift])^[rs.Left + xshift];
rowd := @PQuadArray(Dest.Scanline[rd.Top])^[rd.Left];
for y := 0 to h - 1 do
begin
ts := Rows;
td := rowd;
for x := 0 to w - 1 do
begin
pRGBQuad(td)^ := pRGBQuad(ts)^;
inc(td, bytespp);
inc(ts, xsteps[x]);
end;
Dec(rowd, bd);
Dec(Rows, ysteps[y]);
end;
end;
end
else
begin
{first make arrays of the steps of uniform pixels}
SetLength(xsteps, WS);
SetLength(ysteps, hs);
intscale := round(w / WS * $10000);
x1 := 0;
x2 := (intscale + $7FFF) shr 16;
c := 0;
for i := 0 to WS - 1 do
begin
xsteps[i] := x2 - x1;
x1 := x2;
x2 := ((i + 2) * intscale + $7FFF) shr 16;
if x2 > w then
x2 := w;
if i = WS - 1 then
c := x1;
end;
if c < w then {>is now not possible}
begin
xshift := (w - c) div 2;
yshift := w - c - xshift;
xsteps[WS - 1] := xsteps[WS - 1] + xshift;
xsteps[0] := xsteps[0] + yshift;
end;
intscale := round(h / hs * $10000);
x1 := 0;
x2 := (intscale + $7FFF) shr 16;
c := 0;
for i := 0 to hs - 1 do
begin
ysteps[i] := (x2 - x1);
x1 := x2;
x2 := ((i + 2) * intscale + $7FFF) shr 16;
if x2 > h then
x2 := h;
if i = hs - 1 then
c := x1;
end;
if c < h then
begin
yshift := (h - c) div 2;
ysteps[hs - 1] := ysteps[hs - 1] + yshift;
yshift := h - c - yshift;
ysteps[0] := ysteps[0] + yshift;
end;
if pf = pf24bit then
begin
Rows := @PRGBArray(Src.Scanline[rs.Top])^[rs.Left];
rowd := @PRGBArray(Dest.Scanline[rd.Top])^[rd.Left];
for y := 0 to hs - 1 do
begin
for j := 1 to ysteps[y] do
begin
ts := Rows;
td := rowd;
for x := 0 to WS - 1 do
begin
for i := 1 to xsteps[x] do
begin
pRGBTriple(td)^ := pRGBTriple(ts)^;
inc(td, bytespp);
end;
inc(ts, bytespp);
end;
Dec(rowd, bd);
end;
Dec(Rows, bs);
end;
end
else
begin
Rows := @PQuadArray(Src.Scanline[rs.Top])^[rs.Left];
rowd := @PQuadArray(Dest.Scanline[rd.Top])^[rd.Left];
for y := 0 to hs - 1 do
begin
for j := 1 to ysteps[y] do
begin
ts := Rows;
td := rowd;
for x := 0 to WS - 1 do
begin
for i := 1 to xsteps[x] do
begin
pRGBQuad(td)^ := pRGBQuad(ts)^;
inc(td, bytespp);
end;
inc(ts, bytespp);
end;
Dec(rowd, bd);
end;
Dec(Rows, bs);
end;
end;
end;
end;
end.
How to create a ScanLine implementation of Stretchblt
Answer:
I'm using this routine for animated zooms, so I took special care to keep the stretch centered. In this scenario the simple stretch makes sense and improves performance. For thumbnailing, be aware that when you make a thumbnail from a bmp file from disk, then most of the time is spent on file I/O, the resampling time compared to that is peanuts, same goes for a jpeg, only for those the decoding is what takes long.
unit DeleteScans;
interface
uses
Windows, Graphics;
procedure DeleteScansRect(Src, Dest: TBitmap; rs, rd: TRect);
{ScanLine implementation of Stretchblt/Delete_Scans. About twice as fast.
Stretches Src to Dest, rs is source rect, rd is dest. rect. The stretch is centered,
i.e the center of rs is mapped to the center of rd. Src, Dest are assumed to be bottom up}
implementation
uses
Classes, Math;
type
TRGBArray = array[0..64000] of TRGBTriple;
PRGBArray = ^TRGBArray;
TQuadArray = array[0..64000] of TRGBQuad;
PQuadArray = ^TQuadArray;
procedure DeleteScansRect(Src, Dest: TBitmap; rs, rd: TRect);
var
xsteps, ysteps: array of Integer;
intscale: Integer;
i, x, y, x1, x2, bitspp, bytespp: Integer;
ts, td: PByte;
bs, bd, WS, hs, w, h: Integer;
Rows, rowd: PByte;
j, c: Integer;
pf: TPixelFormat;
xshift, yshift: Integer;
begin
WS := rs.Right - rs.Left;
hs := rs.Bottom - rs.Top;
w := rd.Right - rd.Left;
h := rd.Bottom - rd.Top;
pf := Src.PixelFormat;
if (pf <> pf32Bit) and (pf <> pf24bit) then
begin
pf := pf24bit;
Src.PixelFormat := pf;
end;
Dest.PixelFormat := pf;
if not (((w <= WS) and (h <= hs)) or ((w >= WS) and (h >= hs))) then
{We do not handle a mix of up-and downscaling, using threadsafe StretchBlt instead}
begin
Src.Canvas.Lock;
Dest.Canvas.Lock;
try
SetStretchBltMode(Dest.Canvas.Handle, STRETCH_DELETESCANS);
StretchBlt(Dest.Canvas.Handle, rd.Left, rd.Top, w, h, Src.Canvas.Handle,
rs.Left, rs.Top, WS, hs, SRCCopy);
finally
Dest.Canvas.Unlock;
Src.Canvas.Unlock;
end;
exit;
end;
if pf = pf24bit then
begin
bitspp := 24;
bytespp := 3;
end
else
begin
bitspp := 32;
bytespp := 4;
end;
bs := (Src.Width * bitspp + 31) and not 31;
bs := bs div 8; {BytesPerScanline Source}
bd := (Dest.Width * bitspp + 31) and not 31;
bd := bd div 8; {BytesPerScanline Dest}
if w < WS then {downsample}
begin
{first make arrays of the skipsteps}
SetLength(xsteps, w);
SetLength(ysteps, h);
intscale := round(WS / w * $10000);
x1 := 0;
x2 := (intscale + $7FFF) shr 16;
c := 0;
for i := 0 to w - 1 do
begin
xsteps[i] := (x2 - x1) * bytespp;
x1 := x2;
x2 := ((i + 2) * intscale + $7FFF) shr 16;
if i = w - 2 then
c := x1;
end;
xshift := min(max((WS - c) div 2, -rs.Left), Src.Width - rs.Right);
intscale := round(hs / h * $10000);
x1 := 0;
x2 := (intscale + $7FFF) shr 16;
c := 0;
for i := 0 to h - 1 do
begin
ysteps[i] := (x2 - x1) * bs;
x1 := x2;
x2 := ((i + 2) * intscale + $7FFF) shr 16;
if i = h - 2 then
c := x1;
end;
yshift := min(max((hs - c) div 2, -rs.Top), Src.Height - rs.Bottom);
if pf = pf24bit then
begin
Rows := @PRGBArray(Src.Scanline[rs.Top + yshift])^[rs.Left + xshift];
rowd := @PRGBArray(Dest.Scanline[rd.Top])^[rd.Left];
for y := 0 to h - 1 do
begin
ts := Rows;
td := rowd;
for x := 0 to w - 1 do
begin
pRGBTriple(td)^ := pRGBTriple(ts)^;
inc(td, bytespp);
inc(ts, xsteps[x]);
end;
Dec(rowd, bd);
Dec(Rows, ysteps[y]);
end;
end
else
begin
Rows := @PQuadArray(Src.Scanline[rs.Top + yshift])^[rs.Left + xshift];
rowd := @PQuadArray(Dest.Scanline[rd.Top])^[rd.Left];
for y := 0 to h - 1 do
begin
ts := Rows;
td := rowd;
for x := 0 to w - 1 do
begin
pRGBQuad(td)^ := pRGBQuad(ts)^;
inc(td, bytespp);
inc(ts, xsteps[x]);
end;
Dec(rowd, bd);
Dec(Rows, ysteps[y]);
end;
end;
end
else
begin
{first make arrays of the steps of uniform pixels}
SetLength(xsteps, WS);
SetLength(ysteps, hs);
intscale := round(w / WS * $10000);
x1 := 0;
x2 := (intscale + $7FFF) shr 16;
c := 0;
for i := 0 to WS - 1 do
begin
xsteps[i] := x2 - x1;
x1 := x2;
x2 := ((i + 2) * intscale + $7FFF) shr 16;
if x2 > w then
x2 := w;
if i = WS - 1 then
c := x1;
end;
if c < w then {>is now not possible}
begin
xshift := (w - c) div 2;
yshift := w - c - xshift;
xsteps[WS - 1] := xsteps[WS - 1] + xshift;
xsteps[0] := xsteps[0] + yshift;
end;
intscale := round(h / hs * $10000);
x1 := 0;
x2 := (intscale + $7FFF) shr 16;
c := 0;
for i := 0 to hs - 1 do
begin
ysteps[i] := (x2 - x1);
x1 := x2;
x2 := ((i + 2) * intscale + $7FFF) shr 16;
if x2 > h then
x2 := h;
if i = hs - 1 then
c := x1;
end;
if c < h then
begin
yshift := (h - c) div 2;
ysteps[hs - 1] := ysteps[hs - 1] + yshift;
yshift := h - c - yshift;
ysteps[0] := ysteps[0] + yshift;
end;
if pf = pf24bit then
begin
Rows := @PRGBArray(Src.Scanline[rs.Top])^[rs.Left];
rowd := @PRGBArray(Dest.Scanline[rd.Top])^[rd.Left];
for y := 0 to hs - 1 do
begin
for j := 1 to ysteps[y] do
begin
ts := Rows;
td := rowd;
for x := 0 to WS - 1 do
begin
for i := 1 to xsteps[x] do
begin
pRGBTriple(td)^ := pRGBTriple(ts)^;
inc(td, bytespp);
end;
inc(ts, bytespp);
end;
Dec(rowd, bd);
end;
Dec(Rows, bs);
end;
end
else
begin
Rows := @PQuadArray(Src.Scanline[rs.Top])^[rs.Left];
rowd := @PQuadArray(Dest.Scanline[rd.Top])^[rd.Left];
for y := 0 to hs - 1 do
begin
for j := 1 to ysteps[y] do
begin
ts := Rows;
td := rowd;
for x := 0 to WS - 1 do
begin
for i := 1 to xsteps[x] do
begin
pRGBQuad(td)^ := pRGBQuad(ts)^;
inc(td, bytespp);
end;
inc(ts, bytespp);
end;
Dec(rowd, bd);
end;
Dec(Rows, bs);
end;
end;
end;
end;
end.
2008. március 13., csütörtök
Render a TRichEdit text onto a canvas
Problem/Question/Abstract:
How to render a TRichEdit text onto a canvas?
Answer:
procedure RichEditToCanvas(RichEdit: TRichEdit; Canvas: TCanvas; PixelsPerInch:
Integer);
var
ImageCanvas: TCanvas;
fmt: TFormatRange;
begin
ImageCanvas := Canvas;
with fmt do
begin
hdc := ImageCanvas.Handle;
hdcTarget := hdc;
// rect needs to be specified in twips (1/1440 inch) as unit
rc := Rect(0, 0,
ImageCanvas.ClipRect.Right * 1440 div PixelsPerInch,
ImageCanvas.ClipRect.Bottom * 1440 div PixelsPerInch
);
rcPage := rc;
chrg.cpMin := 0;
chrg.cpMax := RichEdit.GetTextLen;
end;
SetBkMode(ImageCanvas.Handle, TRANSPARENT);
RichEdit.Perform(EM_FORMATRANGE, 1, Integer(@fmt));
// next call frees some cached data
RichEdit.Perform(EM_FORMATRANGE, 0, 0);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
RichEditToCanvas(RichEdit1, Image1.Canvas, Self.PixelsPerInch);
Image1.Refresh;
end;
How to render a TRichEdit text onto a canvas?
Answer:
procedure RichEditToCanvas(RichEdit: TRichEdit; Canvas: TCanvas; PixelsPerInch:
Integer);
var
ImageCanvas: TCanvas;
fmt: TFormatRange;
begin
ImageCanvas := Canvas;
with fmt do
begin
hdc := ImageCanvas.Handle;
hdcTarget := hdc;
// rect needs to be specified in twips (1/1440 inch) as unit
rc := Rect(0, 0,
ImageCanvas.ClipRect.Right * 1440 div PixelsPerInch,
ImageCanvas.ClipRect.Bottom * 1440 div PixelsPerInch
);
rcPage := rc;
chrg.cpMin := 0;
chrg.cpMax := RichEdit.GetTextLen;
end;
SetBkMode(ImageCanvas.Handle, TRANSPARENT);
RichEdit.Perform(EM_FORMATRANGE, 1, Integer(@fmt));
// next call frees some cached data
RichEdit.Perform(EM_FORMATRANGE, 0, 0);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
RichEditToCanvas(RichEdit1, Image1.Canvas, Self.PixelsPerInch);
Image1.Refresh;
end;
2008. március 12., szerda
Working with multiselect grids
Problem/Question/Abstract:
Working with multiselect grids
Answer:
Delphi 2.0 multiselect grids have an undocumented SelectedRows property, a TBookmark list.
You can use it with code like this:
with DbGrid1 do
begin
��for i := 0 to SelectedRows.Count-1 do
begin
����DataSource.DataSet.Bookmark := SelectedRows[i];
����{ the dataset is positioned on the selection. do your stuff }
��end;
end;
2008. március 11., kedd
Use the OnDraw methods of a TListView in vsReport view style
Problem/Question/Abstract:
Does anyone know how to use the OnDraw methods in the TListView - vsReport? I want to draw both the list item and the list item's sub-items, but it seems to me that the OnDraw only gets called on the item. I have tried all the draw methods but cannot realy figure out how to draw in the subitems rect.
Answer:
You are right, it gets called by the TListItem but since you have the TListItem, you can draw the subitems as well. Example DrawItem:
{ ... }
if Item.Index = 0 then
Sender.Canvas.Brush.Color := clRed
else
Sender.Canvas.Brush.Color := clYellow;
Sender.Canvas.FillRect(Rect);
for x := 0 to TListView(Sender).Columns.Count - 1 do
if x = 0 then
Sender.Canvas.TextOut(Rect.Left + 2, Rect.Top, Item.Caption)
else
Sender.Canvas.TextOut((Rect.Left + 2) + Sender.Column[x].Width,
Rect.Top, Item.SubItems.Strings[x - 1]);
{ ... }
2008. március 10., hétfő
How to add items to a TComboBox upon an [ENTER] key press
Problem/Question/Abstract:
I would like my user to be able to enter items into a combobox and add each item upon pressing the enter key. Is there a simple way to do this. I started using csdropdown style. Then I tried using the keydown event with key 13, so that when the user presses enter, the user's entry is added to combobox.items, but so far its not working. I'll keep hacking away at it, but I thought perhaps there is an existing solution to this problem, either starting from a different control, or using a different method in TComboBox.
Answer:
This works for me on a csDropDown combobox (D5.01):
procedure TForm1.ComboBox1KeyPress(Sender: TObject; var Key: Char);
begin
if key = #13 then
begin
if combobox1.text <> emptystr then
combobox1.items.add(combobox1.text);
key := #0;
end;
end;
2008. március 9., vasárnap
How to drop a TComboBox up instead of down
Problem/Question/Abstract:
You know how when you open a TComboBox that is near the bottom of the physical screen, Windows places the list above the ComboBox rather than below. Is there any way in Delphi to force that behavior? In other words, I'm trying to come up with a drop-up TComboBox.
Answer:
Here is some code that may help you:
{ ... }
cbxMaxWidth: integer;
procedure pmAdjustDropList(var Msg: TMessage); message WM_USER + 1800;
{ ... }
procedure TForm1.pmAdjustDropList(var Msg: TMessage); {WM_USER + 1800;}
var
LHnd: HWnd;
rct: TRect;
pt: TPoint;
x: integer;
begin
x := ComboBox1.Height + 1;
ComboBox1.Perform(CB_GETDROPPEDCONTROLRECT, 0, longint(@rct));
pt := Point(rct.Left + 1, rct.Top + x);
{Gets the handle of the window containing the pt}
LHnd := WindowFromPoint(pt);
rct.Left := rct.Right - cbxMaxWidth; {cbxMaxWidth is maximum width for box}
if rct.Right - rct.Left > ComboBox1.Width then
begin
{Up with right side of combobox}
pt := ComboBox1.ScreenToClient(rct.BottomRight);
OffsetRect(rct, ComboBox1.Width - (pt.x), x);
MoveWindow(LHnd, rct.Left, rct.Top, rct.Right - rct.Left, rct.Bottom - rct.Top, true);
end;
end;
procedure TForm1.ComboBox1DropDown(Sender: TObject);
begin
PostMessage(Handle, WM_USER + 1800, 0, 0);
end;
2008. március 8., szombat
How to read the disk ID number
Problem/Question/Abstract:
How do I read system information? In my case I want to read a clients hard disk ID number.
Answer:
Use GetVolumeInformation, yet gets the formatted serial number, not the manufacturers HD number.
procedure TForm1.Button1Click(Sender: TObject);
var
VolumeName, FileSystemName: array[0..MAX_PATH - 1] of Char;
VolumeSerialNo: DWord;
MaxComponentLength, FileSystemFlags: Integer;
begin
GetVolumeInformation('C:\', VolumeName, MAX_PATH, @VolumeSerialNo, MaxComponentLength, FileSystemFlags, FileSystemName, MAX_PATH);
Memo1.Lines.Add('VName = ' + VolumeName);
Memo1.Lines.Add('SerialNo = $ ' + IntToHex(VolumeSerialNo, 8));
Memo1.Lines.Add('CompLen = ' + IntToStr(MaxComponentLength));
Memo1.Lines.Add('Flags = $' + IntToHex(FileSystemFlags, 4));
Memo1.Lines.Add('FSName = ' + FileSystemName);
end;
2008. március 7., péntek
Save and load the state of a TMenuItem to/ from a TIniFile
Problem/Question/Abstract:
How to save and load the state of a TMenuItem to/ from a TIniFile
Answer:
uses
IniFiles;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
ini: TIniFile;
begin
{Save the checked state of each menu item when the form closes}
Ini := TIniFile.Create('mysettings.ini');
Ini.WriteBool('Settings', 'MenuItem1Checked', MenuItem1.Checked);
Ini.WriteBool('Settings', 'MenuItem2Checked', MenuItem2.Checked);
Ini.WriteBool('Settings', 'MenuItem2Checked', MenuItem2.Checked);
Ini.Free;
end;
procedure TForm1.FormShow(Sender: TObject);
var
ini: TIniFile;
begin
{Reload the checked state of each menu item when the form opens}
Ini := TIniFile.Create('mysettings.ini');
MenuItem1.Checked := Ini.ReadBool('Settings', 'MenuItem1Checked', False);
MenuItem2.Checked := Ini.ReadBool('Settings', 'MenuItem2Checked', False);
MenuItem3.Checked := Ini.ReadBool('Settings', 'MenuItem3Checked', False);
Ini.Free;
end;
2008. március 6., csütörtök
Find the parent TTabSheet of a control
Problem/Question/Abstract:
I am trying to write a recursive function that will go through all the parents of a component until it finds the tabsheet that it is on (ie: TEdit -> TGroupBox -> TTabSheet). Then I would like to get the caption of that tabsheet.
Answer:
Solve 1:
If you walk the tree up - from root - you need recursion, but the opposite way is linear as each element (control) has only one immediate parent, so recursion would be nonsense. A code like this should do:
function GetParentTabsheet(C: TControl): TTabsheet;
begin
Result := TTabSheet(C.Parent);
while (Result <> nil) and not Result.InheritsFrom(TTabSheet) do
Result := TTabSheet(Result.Parent);
end;
If you really want it recursive:
function GetParentTabsheet(C: TControl): TTabsheet;
begin
Result := TTabSheet(C.Parent);
if (Result <> nil) and not Result.InheritsFrom(TTabSheet) then
Result := GetParentTabsheet(Result);
end;
Solve 2:
function GetParentTabSheet(Control: TControl): TTabSheet;
begin
while Assigned(Control) and not (Control is TTabSheet) do
Control := Control.Parent;
Result := TTabSheet(Control);
end;
Solve 3:
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage(TTabSheet(TGroupBox(Edit1.Parent).Parent).Caption);
end;
2008. március 5., szerda
How to enter dates into a TDateTimePicker by keyboard only
Problem/Question/Abstract:
We have decided to replace all occurrences of TMaskEdit in our applications with TDateTimePicker's (of course only where they were used for entering dates). The problem is making the transition as easy as possible for the users. TDateTimePicker as it is is not very well-suited for keyboard-only input. The first annoyance is that you have to explicitly enter the separators. TMaskEdit just jumped to the next figure if you entered a number instead of the separator character. It becomes worse still when ShowCheckbox is True. In that case the focus is automatically shifted to the checkbox after having entered the first two digits, essentially making it impossible to enter a date by keyboard only (unless you manually cursor to the every single figure). Does anyone know if it possible at all to overcome these limitations by simply subclassing TDateTimePicker?
Answer:
Here is the routine that I use for date entry edits. Feel free to use it if you just want keyboard entry of dates. Here's the way it works: As the user types in the edit, it's checked against the current ShortDateFormat setting to determine whether it's in the month, day or year portion. If, for instance, they are in the month portion and they type a '3', it knows that it must be the third month and so puts '03' and goes to the next section (if any). If you want to default any portion to the current day, month or year, simply hit the space bar. This gives users a really fast way to fill in dates, especially the current day's. All you need to do is assign the OnKeyPress event of any edit control and make a simple call:
DateKeyPress(self, Key);
{Included because I use it to tab to the next control when the date is complete}
procedure PressTabKey(Shift: boolean = false);
begin
if Shift then
keybd_event(VK_SHIFT, 0, 0, 0);
keybd_event(VK_TAB, 0, 0, 0);
keybd_event(VK_TAB, 0, KEYEVENTF_KEYUP, 0);
if Shift then
keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
end;
procedure DateKeyPress(Sender: TObject; var Key: char);
const
Zero: char = '0';
DateParts: array[1..3] of string = ('', '', '');
SeparatorChar: string = '';
procedure GetDateParts;
var
x, y: integer;
s: string;
c: char;
begin
s := ShortDateFormat;
y := 1;
c := s[1];
for x := 1 to length(s) do
if (s[x] <> DateSeparator) then
begin
if (s[x] <> c) then
begin
c := s[x];
inc(y);
end;
DateParts[y] := DateParts[y] + s[x];
end
else
begin
inc(y);
c := s[x + 1];
end;
if pos(DateSeparator, s) <> 0 then
SeparatorChar := DateSeparator
else
SeparatorChar := '';
end;
function FixDatePart(s: string; Part: integer): string;
begin
if (s <> '') and (s[length(s)] = DateSeparator) then
delete(s, length(s), 1);
if (s = '') then
s := FormatDateTime(DateParts[Part], Now);
if (DateParts[Part][1] in ['m', 'M', 'd', 'D']) then
result := format('%.' + IntToStr(length(DateParts[Part])) + 'd', [StrToInt(s)])
else if (length(s) < length(DateParts[Part])) then
result := copy(FormatDateTime(DateParts[Part], Now), 1,
length(Dateparts[Part]) - length(s)) + s
else
result := s;
end;
var
s: string;
x,
sepLength: integer;
begin
if DateParts[1] = '' then
GetDateParts;
if ord(Key) in ActionKeys then
exit;
s := copy(TEdit(Sender).Text, 1, TEdit(Sender).SelStart);
x := length(s);
sepLength := length(SeparatorChar);
case Key of
' ':
begin
if (x = length(DateParts[1]) + sepLength) then
s := s + FixDatePart('', 2) + SeparatorChar
else if (x = length(DateParts[1] + DateParts[2]) + (sepLength * 2)) then
s := s + FixDatePart('', 3)
else if (x = 0) then
s := FixDatePart('', 1) + SeparatorChar
else if (x <= length(DateParts[1])) then
s := FixDatePart(s, 1) + SeparatorChar + FixDatePart('', 2) + SeparatorChar
else if (x <= (length(DateParts[1] + DateParts[2]) + (sepLength * 2))) then
s := copy(s, 1, length(DateParts[1]) + sepLength) + FixDatePart(copy(s, length(DateParts[1]) + sepLength + 1, length(s)), 2) + SeparatorChar + FormatDateTime(DateParts[3], Now)
else
s := copy(s, 1, length(DateParts[1] + DateParts[2]) + (sepLength * 2)) +
FixDatePart(copy(s, length(DateParts[1] + DateParts[2]) +
(sepLength * 2) + 1, length(s)), 3);
TEdit(Sender).Text := s;
Key := #0;
TEdit(Sender).SelStart := length(s);
end;
'0'..'9':
begin
if (x in [length(DateParts[1]), length(DateParts[1] + DateParts[2]) + sepLength]) then
s := s + SeparatorChar + Key
else if (x = 0) and (((DateParts[1][1] in ['m', 'M']) and (Key in ['2'..'9'])) or((DateParts[1][1] in ['d', 'D']) and (Key in ['4'..'9']))) then
s := FixDatePart(Key, 1) + SeparatorChar
else if (x = length(DateParts[1]) + sepLength) and (((DateParts[2][1] in ['m', 'M']) and (Key in ['2'..'9'])) or ((DateParts[2][1] in ['d', 'D']) and (Key in ['4'..'9']))) then
s := s + FixDatePart(Key, 2) + SeparatorChar
else
s := s + Key;
if (length(s) = length(DateParts[1])) or (length(s) = length(DateParts[1] + DateParts[2]) + sepLength) then
s := s + SeparatorChar;
TEdit(Sender).Text := s;
Key := #0;
TEdit(Sender).SelStart := length(s);
end;
{ uncomment this to use N/A values
'n','N':
begin
TEdit(Sender).Text := 'N/A';
TEdit(Sender).SelStart := 0;
TEdit(Sender).SelLength := 3;
Key := #0;
end;}
else
begin
if (Key = DateSeparator) then
begin
if s[x] <> DateSeparator then
begin
if x = length(DateParts[1]) - 1 then
s := Zero + s + DateSeparator
else if x = 4 then
begin
insert(Zero, s, 4);
s := s + '/';
end;
end;
TEdit(Sender).Text := s;
Key := #0;
TEdit(Sender).SelStart := length(s);
end
else
Key := #0;
end;
end;
if length(TEdit(Sender).Text) = length(ShortDateFormat) then
PressTabKey;
end;
2008. március 4., kedd
Convert your boolean values to the meaningful words
Problem/Question/Abstract:
How I convert boolean values to the words depending on the situation? For example, TRUE here means "Enabled", and FALSE there means "Failed"?
Answer:
Solve 1:
Here is the code snippet to do the job. If the second parameter is omitted, the function returns "TRUE" or "FALSE".
Modify the function declaration to change the default returning values. Expand TBooleanWordType and BooleanWord definitions to include more specific values if needed.
interface
{...}
type
TBooleanWordType =
(bwTrue, bwYes, bwOn, bwEnabled, bwSuccessful, bwOK, bwOne);
{...}
function BoolToStr(AValue: boolean;
ABooleanWordType: TBooleanWordType = bwTrue): string;
{...}
{=====================================================}
implementation
{...}
const
BooleanWord: array[boolean, TBooleanWordType] of string =
(
('FALSE', 'No', 'Off', 'Disabled', 'Failed', 'Cancel', '0'),
('TRUE', 'Yes', 'On', 'Enabled', 'Successful', 'OK', '1')
);
{...}
{-----------------------------------------------------}
function BoolToStr(AValue: boolean;
ABooleanWordType: TBooleanWordType = bwTrue): string;
begin
Result := BooleanWord[AValue, ABooleanWordType];
end; {--BoolToStr--}
{...}
Solve 2:
interface
function BoolToStr(b: boolean; TrueValue: string = '1'; FalseValue: string = '0'):
string; overload;
implementation
function BoolToStr(b: boolean; TrueValue: string = '1'; FalseValue: string = '0'):
string; overload;
begin
if b then
Result := TrueValue
else
Result := FalseValue,
end;
// example for italian language
s := BoolToStr(CheckBox1.Checked, 'Si', 'No');
Add this overloaded Function to the unit.
Solve 3:
const
arrBooleanValues: array[Boolean] of ShortString = ('False', 'True');
var
b: Boolean;
s: string;
begin
b := False;
s := arrBooleanValues[b]; // 'False'
b := True;
s := arrBooleanValues[b]; // 'True'
end;
2008. március 3., hétfő
Read and write icon files
Problem/Question/Abstract:
How to read and write icon files
Answer:
{ icon. pas}
unit Icons;
interface
uses
windows, sysutils;
type
PByte = ^Byte;
PBitmapInfo = ^BitmapInfo;
{These first two structs represent how the icon information is stored when it is
bound into a EXE or DLL file. Structure members are WORD aligned and the last
member of the structure is the ID instead of the imageoffset.}
type
PMEMICONDIRENTRY = ^TMEMICONDIRENTRY;
TMEMICONDIRENTRY = packed record
bWidth: Byte; {Width of the image}
bHeight: Byte; {Height of the image (times 2) }
bColorCount: Byte; {Number of colors in image (0 if >=8bpp) }
bReserved: Byte; {Reserved}
wPlanes: WORD; {Color Planes}
wBitCount: WORD; {Bits per pixel}
dwBytesInRes: DWORD; {How many bytes in this resource?}
nID: WORD; {The ID}
end;
type
PMEMICONDIR = ^TMEMICONDIR;
TMEMICONDIR = packed record
idReserved: WORD; {Reserved}
idType: WORD; {Resource type (1 for icons) }
idCount: WORD; {How many images?}
idEntries: array[0..10] of TMEMICONDIRENTRY; {The entries for each image}
end;
{These next two structs represent how the icon information is stored in an ICO file.}
type
PICONDIRENTRY = ^TICONDIRENTRY;
TICONDIRENTRY = packed record
bWidth: Byte; {Width of the image}
bHeight: Byte; {Height of the image (times 2) }
bColorCount: Byte; {Number of colors in image (0 if >=8bpp) }
bReserved: Byte; {Reserved}
wPlanes: WORD; {Color Planes}
wBitCount: WORD; {Bits per pixel}
dwBytesInRes: DWORD; {How many bytes in this resource?}
dwImageOffset: DWORD; {Where in the file is this image}
end;
type
PICONDIR = ^TICONDIR;
TICONDIR = packed record
idReserved: WORD; {Reserved}
idType: WORD; {Resource type (1 for icons) }
idCount: WORD; {How many images?}
idEntries: array[0..0] of TICONDIRENTRY; {The entries for each image}
end;
{The following two structs are for the use of this program in manipulating icons.
They are more closely tied to the operation of this program than the structures
listed above. One of the main differences is that they provide a pointer to the
DIB information of the masks.}
type
PICONIMAGE = ^TICONIMAGE;
TICONIMAGE = packed record
Width, Height, Colors: UINT; {Width, Height and bpp}
lpBits: Pointer; {ptr to DIB bits}
dwNumBytes: DWORD; {How many bytes?}
pBmpInfo: PBitmapInfo;
end;
{
TICONIMAGE = packed record
Width, Height, Colors: UINT; {Width, Height and bpp}
lpBits: pointer; {ptr to DIB bits}
dwNumBytes: DWORD; {How many bytes?}
lpbi: PBITMAPINFO; {ptr to header}
lpXOR: LPBYTE; {ptr to XOR image bits}
lpAND: LPBYTE; {ptr to AND image bits}
end;
}
type
PICONRESOURCE = ^TICONRESOURCE;
TICONRESOURCE = packed record
nNumImages: UINT; {How many images?}
IconImages: array[0..10] of TICONIMAGE; {Image entries}
end;
{
TICONRESOURCE = packed record
bHasChanged: BOOL; {Has image changed?}
szOriginalICOFileName: array[0..MAX_PATH] of Char; {Original name}
szOriginalDLLFileName: array[0..MAX_PATH] of Char; {Original name}
nNumImages: UINT; {How many images?}
IconImages: array[0..0] of ICONIMAGE; {Image entries}
end;
}
type
TPageInfo = packed record
Width: Byte;
Height: Byte;
ColorQuantity: Integer;
Reserved: DWORD;
PageSize: DWORD;
PageOffSet: DWORD;
end;
type
TPageDataHeader = packed record
PageHeadSize: DWORD;
XSize: DWORD;
YSize: DWORD;
SpeDataPerPixSize: Integer;
ColorDataPerPixSize: Integer;
Reserved: DWORD;
DataAreaSize: DWORD;
ReservedArray: array[0..15] of Char;
end;
type
TIcoFileHeader = packed record
FileFlag: array[0..3] of Byte;
PageQuartity: Integer;
PageInfo: TPageInfo;
end;
{function WriteIconToFile(Bitmap: hBitmap; Icon: hIcon; szFileName: string): Boolean; overload;}
function ExtractIconFromFile(ResFileName: string; IcoFileName: string; nIndex:
string): Boolean;
function WriteIconResourceToFile(hFile: hwnd; lpIR: PICONRESOURCE): Boolean;
implementation
function WriteICOHeader(hFile: HWND; nNumEntries: UINT): Boolean;
type
TFIcoHeader = record
wReserved: WORD;
wType: WORD;
wNumEntries: WORD;
end;
var
IcoHeader: TFIcoHeader;
{Output: WORD;}
dwBytesWritten: DWORD;
begin
Result := False;
IcoHeader.wReserved := 0;
IcoHeader.wType := 1;
IcoHeader.wNumEntries := WORD(nNumEntries);
if not WriteFile(hFile, IcoHeader, SizeOf(IcoHeader), dwBytesWritten, nil) then
begin
MessageBox(0, pchar(SysErrorMessage(GetLastError)), 'info', MB_OK);
exit;
end;
if dwBytesWritten <> SizeOf(IcoHeader) then
exit;
{
Output := 0;
{Write 'reserved' WORD}
if not WriteFile(hFile, Output, SizeOf(WORD), dwBytesWritten, nil) then
exit;
{Did we write a WORD?}
if dwBytesWritten <> SizeOf(WORD) then
exit;
{Write 'type' WORD (1) }
Output := 1;
if not WriteFile(hFile, Output, SizeOf(WORD), dwBytesWritten, nil) then
exit;
if dwBytesWritten <> SizeOf(WORD) then
exit;
{Write Number of Entries}
Output := WORD(nNumEntries);
if not WriteFile(hFile, Output, SizeOf(WORD), dwBytesWritten, nil) then
exit;
if dwBytesWritten <> SizeOf(WORD) then
exit;
}
Result := True;
end;
function CalculateImageOffset(lpIR: PICONRESOURCE; nIndex: UINT): DWORD;
var
dwSize: DWORD;
i: Integer;
begin
{Calculate the ICO header size}
dwSize := 3 * sizeof(WORD);
{Add the ICONDIRENTRY's}
inc(dwSize, lpIR.nNumImages * sizeof(TICONDIRENTRY));
{Add the sizes of the previous images}
for i := 0 to nIndex - 1 do
inc(dwSize, lpIR.IconImages[i].dwNumBytes);
{We're there - return the number}
Result := dwSize;
end;
function WriteIconResourceToFile(hFile: hwnd; lpIR: PICONRESOURCE): Boolean;
var
i: UINT;
dwBytesWritten: DWORD;
ide: TICONDIRENTRY;
dwTemp: DWORD;
begin
{Open the file}
Result := False;
{Write the ICONDIRENTRY's}
for i := 0 to lpIR^.nNumImages - 1 do
begin
{Convert internal format to ICONDIRENTRY}
ide.bWidth := lpIR^.IconImages[i].Width;
ide.bHeight := lpIR^.IconImages[i].Height;
ide.bReserved := 0;
ide.wPlanes := lpIR^.IconImages[i].pBmpInfo.bmiHeader.biPlanes;
ide.wBitCount := lpIR^.IconImages[i].pBmpInfo.bmiHeader.biBitCount;
if ide.wPlanes * ide.wBitCount >= 8 then
ide.bColorCount := 0
else
ide.bColorCount := 1 shl (ide.wPlanes * ide.wBitCount);
ide.dwBytesInRes := lpIR^.IconImages[i].dwNumBytes;
ide.dwImageOffset := CalculateImageOffset(lpIR, i);
{Write the ICONDIRENTRY out to disk}
if not WriteFile(hFile, ide, sizeof(TICONDIRENTRY), dwBytesWritten, nil) then
exit;
{Did we write a full ICONDIRENTRY ?}
if dwBytesWritten <> sizeof(TICONDIRENTRY) then
exit;
end;
{Write the image bits for each image}
for i := 0 to lpIR^.nNumImages - 1 do
begin
dwTemp := lpIR^.IconImages[i].pBmpInfo^.bmiHeader.biSizeImage;
{Set the sizeimage member to zero}
lpIR^.IconImages[i].pBmpInfo^.bmiHeader.biSizeImage := 0;
{Write the image bits to file}
if not WriteFile(hFile, lpIR^.IconImages[i].lpBits^,
lpIR^.IconImages[i].dwNumBytes,
dwBytesWritten, nil) then
exit;
if dwBytesWritten <> lpIR^.IconImages[i].dwNumBytes then
exit;
{Set it back}
lpIR^.IconImages[i].pBmpInfo^.bmiHeader.biSizeImage := dwTemp;
end;
Result := True;
end;
function AWriteIconToFile(bitmap: hBitmap; Icon: hIcon; szFileName: string): Boolean;
var
fh: file of Byte;
IconInfo: _ICONINFO;
PageInfo: TPageInfo;
PageDataHeader: TPageDataHeader;
IcoFileHeader: TIcoFileHeader;
BitsInfo: tagBITMAPINFO;
p: Pointer;
PageDataSize: Integer;
begin
Result := False;
GetIconInfo(Icon, IconInfo);
AssignFile(fh, szFileName);
FileMode := 1;
Reset(fh);
GetDIBits(0, Icon, 0, 32, nil, BitsInfo, DIB_PAL_COLORS);
GetDIBits(0, Icon, 0, 32, p, BitsInfo, DIB_PAL_COLORS);
PageDataSize := SizeOf(PageDataHeader) + BitsInfo.bmiHeader.biBitCount;
PageInfo.Width := 32;
PageInfo.Height := 32;
PageInfo.ColorQuantity := 65535;
Pageinfo.Reserved := 0;
PageInfo.PageSize := PageDataSize;
PageInfo.PageOffSet := SizeOf(IcoFileHeader);
IcoFileHeader.FileFlag[0] := 0;
IcoFileHeader.FileFlag[1] := 0;
IcoFileHeader.FileFlag[2] := 1;
IcoFileHeader.FileFlag[3] := 0;
IcoFileHeader.PageQuartity := 1;
IcoFileHeader.PageInfo := PageInfo;
FillChar(PageDataHeader, SizeOf(PageDataHeader), 0);
PageDataHeader.XSize := 32;
PageDataHeader.YSize := 32;
PageDataHeader.SpeDataPerPixSize := 0;
PageDataHeader.ColorDataPerPixSize := 32;
PageDataHeader.PageHeadSize := SizeOf(PageDataHeader);
PageDataHeader.Reserved := 0;
PageDataHeader.DataAreaSize := BitsInfo.bmiHeader.biBitCount;
BlockWrite(fh, IcoFileHeader, SizeOf(IcoFileHeader));
BlockWrite(fh, PageDataHeader, SizeOf(PageDataHeader));
BlockWrite(fh, p, BitsInfo.bmiHeader.biBitCount);
CloseFile(fh);
end;
function AdjustIconImagePointers(lpImage: PICONIMAGE): Bool;
begin
if lpImage = nil then
begin
Result := False;
exit;
end;
lpImage.pBmpInfo := PBitMapInfo(lpImage^.lpBits);
lpImage.Width := lpImage^.pBmpInfo^.bmiHeader.biWidth;
lpImage.Height := (lpImage^.pBmpInfo^.bmiHeader.biHeight) div 2;
lpImage.Colors := lpImage^.pBmpInfo^.bmiHeader.biPlanes *
lpImage^.pBmpInfo^.bmiHeader.biBitCount;
Result := true;
end;
function ExtractIconFromFile(ResFileName: string; IcoFileName: string; nIndex:
string): Boolean;
var
h: HMODULE;
lpMemIcon: PMEMICONDIR;
lpIR: TICONRESOURCE;
src: HRSRC;
Global: HGLOBAL;
i: Integer;
hFile: HWND;
begin
Result := False;
hFile := CreateFile(pchar(IcoFileName), GENERIC_WRITE, 0, nil, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, 0);
if hFile = INVALID_HANDLE_VALUE then
exit; {Error Create File}
h := LoadLibraryEx(pchar(ResFileName), 0, LOAD_LIBRARY_AS_DATAFILE);
if h = 0 then
exit;
try
src := FindResource(h, pchar(nIndex), RT_GROUP_ICON);
if src = 0 then
Src := FindResource(h, Pointer(StrToInt(nIndex)), RT_GROUP_ICON);
if src <> 0 then
begin
Global := LoadResource(h, src);
if Global <> 0 then
begin
lpMemIcon := LockResource(Global);
if Global <> 0 then
begin
{lpIR := @IR;}
try
lpIR.nNumImages := lpMemIcon.idCount;
{Write the header}
for i := 0 to lpMemIcon^.idCount - 1 do
begin
src := FindResource(h, MakeIntResource(lpMemIcon^.idEntries[i].nID),
RT_ICON);
if src <> 0 then
begin
Global := LoadResource(h, src);
if Global <> 0 then
begin
lpIR.IconImages[i].dwNumBytes := SizeofResource(h, src);
GetMem(lpIR.IconImages[i].lpBits, lpIR.IconImages[i].dwNumBytes);
CopyMemory(lpIR.IconImages[i].lpBits, LockResource(Global),
lpIR.IconImages[i].dwNumBytes);
if not AdjustIconImagePointers(@(lpIR.IconImages[i])) then
exit;
end;
end;
end;
if WriteICOHeader(hFile, lpIR.nNumImages) then {No Error Write File}
if WriteIconResourceToFile(hFile, @lpIR) then
Result := True;
finally
for i := 0 to lpIR.nNumImages - 1 do
if assigned(lpIR.IconImages[i].lpBits) then
FreeMem(lpIR.IconImages[i].lpBits);
end;
end;
end;
end;
finally
FreeLibrary(h);
end;
CloseHandle(hFile);
end;
end.
2008. március 2., vasárnap
Detect whether your program runs in the IDE
Problem/Question/Abstract:
Detect whether your program runs in the IDE
Answer:
Below is another way to detect whether your program runs in the Delphi IDE
program p;
begin
if DebugHook <> 0 then
ShowMessage('Running in Delphi IDE');
end.
2008. március 1., szombat
How can I tell the system to rename a file on the next reboot?
Problem/Question/Abstract:
How can I tell the system to rename a file on the next reboot?
Answer:
Windows has a mechanism to replace files that are currently in use on next boot. So what the program could do is
save the updated version under another name, preferably in the applications directory (source and target need to be on the same volume).
the execute the following code:
if Win32Platform = VER_PLATFORM_WIN32_NT then
MoveFileEx(Pchar(tempFilenameWithPath),
Pchar(realfilenamewithpath),
MOVEFILE_REPLACE_EXISTING or MOVEFILE_DELAY_UNTIL_REBOOT)
else
WritePrivateProfileString(
'rename',
Pchar(realfilenamewithpath),
Pchar(tempFilenameWithPath),
'wininit.ini');
Note that the wininit.ini file needs to be set up using the short (DOS 8.3) versions of long path and filenames.
Feliratkozás:
Bejegyzések (Atom)