2006. szeptember 30., szombat
How to draw on the Desktop
Problem/Question/Abstract:
I'd like to draw on the screen, and not necessarily in the application form. Example: When the application is running but the form is minimized, I'd like to be able to draw a circle on the desktop.
Answer:
procedure THovedForm.Tegn1ButtonClick(Sender: TObject);
var
DesktopDC: HDC;
Rectangle: TRect;
pcTekst: PChar;
begin
DesktopDC := GetWindowDC(GetDesktopWindow);
MoveToEx(DesktopDC, 0, 0, nil);
LineTo(DesktopDC, Screen.Width, Screen.Height);
MoveToEx(DesktopDC, 0, Screen.Height, nil);
LineTo(DesktopDC, Screen.Width, 0);
pcTekst := 'Finn Tolderlund';
SetTextColor(DesktopDC, clBlue);
Rectangle.Left := 150;
Rectangle.Top := 250;
Rectangle.Right := 150 + 100;
Rectangle.Bottom := 250 + 100;
SetBkMode(DesktopDC, Transparent);
DrawTextEx(DesktopDC, pcTekst, -1, Rectangle, DT_CENTER or DT_NOCLIP, nil);
ReleaseDC(GetDesktopWindow, DesktopDC);
end;
2006. szeptember 29., péntek
How to get a list of Parallel ports on a PC
Problem/Question/Abstract:
How to get a list of Parallel ports on a PC
Answer:
function PortExists(const PortName: string): Boolean;
var
hPort: HWND;
begin
Result := False;
hPort := CreateFile(PChar(PortName), {name}
GENERIC_READ or GENERIC_WRITE, {access attributes}
0, {no sharing}
nil, {no security}
OPEN_EXISTING, {creation action}
FILE_ATTRIBUTE_NORMAL or
FILE_FLAG_OVERLAPPED, {attributes}
0); {no template}
if hPort <> INVALID_HANDLE_VALUE then
begin
CloseHandle(hPort);
Result := True;
end;
end;
{Parallel Ports}
for i := 1 to 9 do
begin
if PortExists('LPT' + IntToStr(i)) then
List.Append('Ports: Printer Port (LPT' + IntTostr(i) + ')');
end;
2006. szeptember 28., csütörtök
Inside Delphi's Classes and Interfaces Part II
Problem/Question/Abstract:
You've probably used classes & interfaces more than once in your delphi programs. Did you ever dtop to think how delphi implements this creatures ?
Answer:
Inorder to understand this article, you must read the previous article (Inside Delphi's Classes and Interfaces Part I).
In this article we'll finish covering Delphi's implementation of Interfaces, and review a few usefull conclusions.
Let's start with an indepth example :
type
IInterface1 = interface
procedure ActA;
procedure ActB;
end;
IInterface2 = interface(IInterface1)
procedure ActC;
procedure ActD; stdcall;
end;
TSampleClass = class(TInterfacedObject, IInterface1, IInterface2)
procedure ActA;
procedure ActB;
procedure ActC;
procedure ActD; stdcall;
end;
var
Interface1: IInterface1;
Interface2: IInterface2;
Sample: TSampleClass;
begin
Sample := TSampleClass.Create;
Interface1 := Sample;
Interface2 := Sample;
Interface1.ActA;
Interface1.ActB;
Interface2.ActA;
Interface2.ActB;
Interface2.ActC;
Interface2.ActD;
end;
Instead of looking at the compiled code for this example, I'll simlpy note the interesting aspects of it. First, when assigning a value to Interface1, we'd expect delphi to take the value of what 'Sample' points to and add a specific amount ($10) and be done with it. When assigning a value to Interface2, we'd expect delphi to do the same, just add a smaller amount ($0C) because the interfaces are stored in memory from the last to the first.
But delphi doesn't do that. It assignes both Interface1 AND Interface2 the value that 'Sample' points to plus $0C. That's because IInterface2 inherites from IInterface1. Therefor, IInterface2 includes IInterface1. Hence, any call to Interface1, will actually be executed through IInterface2's method list.
Second, when we call Interface1.ActA, it calles the 4th (every interface inherites from IUnknown) method on IInterface2's method list (because IInterface2 inherites from IInterface1). When we call Interface1.ActB it calles the 5th method on IInterface2's method list. When we call Interface2.ActA it calles the 4th method on IInterface2's method list, just the same as Interface1.ActA. That's because IInterface2 inherites from IInterface1.
Third, when we call Interface2.ActD delphi addes one additional instruction before calling the 7th method of IInterface2. That's because we've declared a different convention call to the method (stdcall). Notice that all of IUnknown's methods are defined with the stdcall directive.
The structor of an interface's method list always follows the following rule :
First Method
.
.
Last Method
The parent's interface's method list
In our case, IInterface2's method list is as follows :
ActC
ActD
// IInterface1's method list
ActA
ActB
// IUnknown's method List
QueryInterface
_AddRef
_Release
NOTE : The structor above is how the methods' code is organized in memory. The first entry in any interface's method list will belong to QueryInterface (the first method of IUnknown) but it will point to a place in memory (the implementation of that specific interface's QueryItnerface method) that is higher than the interfaces' own methods' implementation - as shown in the structor above. In our case, IInterface2's QueryInterface's implementation is higher in memory than IInterface2's ActB's implementation, which is higher in memory than ActD's implementation. Thou ActD is the 7th entry, ActB is the 5th entry and QueryInterface is the 1st entry in IInterface2's method list.
To fully understand what happens when delphi calls an interface's method, lets have a look at the compiled method list of IInterface2 in the example above. The following code is an exact copy of the compiled code (except for the comments) :
// ActC
add eax, -$0C
jmp TSampleClass.ActC
// ActD
add dword ptr[esp + $04], -$0C
jmp TSampleClass.ActD
// ActA
add eax, -$0C
jmp TSampleClass.ActA
// ActB
add eax, -$0C
jmp TSampleClass.ActB
// QueryInterface
add dword ptr[esp + $04], -$0C
jmp TInterfacedObject.QueryInterface
// _AddRef
add dword ptr[esp + $04], -$0C
jmp TInterfacedObject._AddRef
// _Release
add dword ptr[esp + $04], -$0C
jmp TInterfacedObject._Release
As you remember, an object's method is actually a regular function/procedure that accepts as a parameter an instance of the method's class. As you can notice, before each call to the real method ('TSampleClass.ActD' for example) there is one line of code that changes the value of either 'eax', or 'dword ptr [esp + $04]', depending on the calling convention. As you can notice, in all cases we subtract $0C form a variable. But, why 12 ($0C = 12) ? That's because this interface (IInterface2) is in the 3rd (FRefcount, IUnknown are before it) place after the pointer to VMT of the clasS TSampleClass. Therefore, the value of any instance of IInterface2 of TSampleClass (Interface2 for example) is actually the value of the pointer to that class' instance plus 12.
Here is another example that will help understand the section above. The following code continues the defenitions from the above code :
type
IAnotherInterface = interface
procedure ActE;
end;
TAnotherSample = class(TInterfacedObject, IInterface2, IAnotherInterface)
procedure ActA;
procedure ActB;
procedure ActC;
procedure ActD; stdcall;
procedure ActE;
end;
var
Interface2: IInterface2;
begin
Interface2 := TAnotherSample.Create;
Interface2.ActC;
end;
Now, let's compare the entry for this example's IInterface2 and the previous' one :
IInterface2 of TAnotherSample:
add eax, -$10
jmp TAnotherSample.ActC
IInterface2 of TSampleClass:
add eax, -$0C
jmp TSampleClass.ActC
There are two obvious changes :
The actuall function that is called (either TAnotherSample.ActC or TSampleClass.ActC)
The amount that 'eax' is changed by. Notice that when calling IInterface2 of TAnotherSample, 'eax' is changed by 16 ($10 = 16) as opposed to being changed by 12. That's because on TAnotherSample, the IInterface2 is the second interface in the instance's structor in memory, and therefor it is "farther away" from the instance itself and needs to be changed by additional 4 bytes.
And now to some usefull sutff :
First, if you want to check if 2 (or more) interface variables are of the same instance, you cannot simply compare them, even if they are of the same type. You must QueryInterface them to a single interface type, and then compare. As a general rule, if you want to compare interfaces, QueryInterface them to IUnknown and then compare.
Example :
type
IBooA = interface
end;
IBooB = interface
end;
TBoo = class(TInterfacedObject, IBooA, IBooB)
end;
var
Boo: TBoo;
BooA: IBooA;
BooB: IBooB;
begin
Boo := TBoo.create;
BooA := Boo;
BooB := Boo;
// This won't complie
if BooA = BooB then
begin
Beep;
end;
if Integer(BooA) = Integer(BooB) then
begin
// will never get here
Beep;
end;
if IUnknown(BooA) = IUnknown(BooB) then
begin
// will never get here
Beep;
end;
// the 'as' word is the same as QueryInterface when acting on interfaces
if (BooA as IUnknown) = (BooB as IUnknown) then
begin
// Will always get here
Beep;
end;
end;
Explaination : The first comparing won't complie, becuase BooA and BooB are of 2 different types. The Second and third comparings will complie but never return true. That's because type casting doesn't change the value of the variable that's being type casted. It only allows the complier to complie the code though there are two different types involved. Hence, if BooA is different from BooB, comparing them will never return true, no matter what type casting is done to them.
But why do BooA and BooB have different values ? They were both assigned using the ":= Boo;" statment. The answer is simple. Remeber that I said that an interface's variable's value is actually the value of the instance itself (or at least the value of the pointer to the instance) plus a different number for each interface ? In our case, BooA is the same as what Boo points to, added 16. And BooB is the same as what Boo points to, added 12. That's why BooA and BooB are not that same.
The Forth comparing actually works. That's because if an interface is from the same type, then comparing it to an interface of that type will always return the expected result (if both interfaces were aquired via QueryInterface, not by type casting). That's because if they are of the same type, then the difference between them and the instance is the same. And if they are of the same instance, then they must be equal.
That is, each interface is equal to it's instance + a specific Delta (the Delta depeneds on the interface). In other words, Interface = Instace + Delta. If 'Instance' is the same for both interfaces, and the 'Delta' is the same (cause they are of the same interface type), then both interfaces must be equal.
Note : This is the way delphi works, for good and for bad. You should take this in mind when writing code for propertys of interface type. The following code wouldn't work properly :
TSample = class
private
FData: IUnknown;
procedure SetData(Value: IUnknown);
protected
procedure Changed; virtual; abstract;
public
property Data: IUnknown read FData write SetData;
end;
procedure TSample.SetData(Value: IUnknown);
begin
// This is incorrect.
if Value <> FData then
begin
FData := Value;
Changed;
end;
end;
It might seem that this code should work, but it might not work when someone would assgin the property 'Data' with an IUnknown retreived by a type cast. The correct code should be :
procedure TSample.SetData(Value: IUnknown);
begin
if (Value as IUnknown) <> (FData as IUnknown) then
begin
FData := Value;
Changed;
end;
end;
Second, each interface you declare that a class implements (with exception of interfaces that inherite from other interfaces) means that each instance of that class will take up 4 more byte of memory. That might seem like nothing (and probably is) except for one case. Consider the following code :
IInterfaceA = interface
end;
IInterfaceB = interface
end;
TSampleClass1 = class(TInterfacedObject, IInterfaceA)
end;
TSampleClass2 = class(TSampleClass1, IInterfaceA, IInterfaceB)
end;
It would seem that each instance of TSampleClass1 should take up 16 bytes, and each instance of TSampleClass2 should take up 20 bytes (4 bytes more, because it supports one more interface). That is not true. Each instance of TSampleClass1 does take up 16 byte. But, each instance of TSampleClass2 takes up 24 bytes ! That's because delphi creates an interface entry even for interfaces that are already implemented by parent classes.
The solution to this is simple. Just remove the decleration of IInterfaceA from TSampleClass2. This will not change the fact that TSampleClass2 implements IInterfaceA, cause TSamlpeClass2 inherites from TSamlpeClass1, which implements IInterface1. This wouldn't have happened if IInterfaceB was a decendant of IInterfaceA.
This might add up to quit alot if you do your inheritence improporely. For example :
TSampleClass1 = class(TInterfacedObject, IUnknown)
end;
TSampleClass2 = class(TSampleClass1, IUnknown)
end;
TSampleClass3 = class(TSampleClass2, IUnknown)
end;
TSampleClass4 = class(TSampleClass3, IUnknown)
end;
TSampleClass5 = class(TSampleClass4, IUnknown)
end;
Each instance of TSampleClass5 takes up 32 bytes of memory, though it has no real data (except for FRefCount of TItnerfacedObject).
2006. szeptember 27., szerda
How to assign multiple TEdit fields to variables
Problem/Question/Abstract:
Is there an easier way to assign multiple Edit fields to variables without individually setting each one? Here is a sample code.
type
testrec = record
fees: array[1..10] of string[65];
end;
var
dat: testrec;
procedure FormToDat;
begin
fees[1] := Edit1.Text;
fees[2] := Edit2.Text;
fees[3] := Edit3.Text;
fees[4] := Edit4.Text;
{ ... }
end;
This sample code seems inefficient and I'm thinking there might be an easier way to do this.
Answer:
There are a wide variety of ways to do this in Delphi, here's one:
var
I: Integer;
C: TComponent;
begin
for I := 1 to 10 do
begin
C := FindComponent('Edit' + IntToStr(I));
if C is TEdit then
TEdit(C).Text := Fees[1];
end;
end;
You could also store references to the edits in a TList or an array, or you could also iterate through the Controls or Components properties.
2006. szeptember 26., kedd
Enabling a horizontal scrollbar in a TListBox
Problem/Question/Abstract:
Enabling a horizontal scrollbar in a TListBox
Answer:
Solve 1:
There is no such property in TListBox. To force a listbox to have horizontal scrollbars, use the message LB_SETHORIZONTALEXTENT.
// e.g. in FormCreate(..)
begin
ListBox1.Width := 300;
// listbox can be scrolled by 100 pixels horizontally now:
SendMessage(ListBox1.Handle, LB_SETHORIZONTALEXTENT, 400, 0);
end;
Solve 2:
MaxWidth := 0;
for i := 0 to ListBox1.Items.Count - 1 do
if MaxWidth < ListBox1.Canvas.TextWidth(ListBox1.Items.Strings[i]) then
MaxWidth := ListBox1.Canvas.TextWidth(ListBox1.Items.Strings[i]);
SendMessage(ListBox1.Handle, LB_SETHORIZONTALEXTENT, MaxWidth + 100, 0);
It uses the Messages .dcu.
2006. szeptember 25., hétfő
Capturing all of the Output from a Console application (16 bit)
Problem/Question/Abstract:
In my article "Capturing all of the Output from a Console application (32 bit)" posted a function for retrieving all of the output of a console application. Unfortunately, though it worked fine on 32-bit apps it did not work well with 16-bit apps. This was not a problem with the code, but rather a bug in Windows (http://support.microsoft.com/support/kb/articles/Q150/9/56.ASP). But how can this be done?
Answer:
Here is new function I have been working on which seems to do the trick. It bypasses the problem with 16-bit apps by directing windows to send the output to a text file, and then reads it back in, deletes the file, and sends the result back to you. Be careful when calling this with command.com. Because it waits on the process infinitely it will hang on this because command.com waits for user input...
Special thanks to Theo Bebekis for his help on this.
If you have questions or comments please email me at johnwlong@characterlink.net, I have not had a chance to thoroughly test this version so any feed back would be helpful.
function GetConsoleOutput(const CommandLine: string): string;
var
SA: TSecurityAttributes;
SI: TStartupInfo;
PI: TProcessInformation;
StdOutFile, AppProcess, AppThread: THandle;
RootDir, WorkDir, StdOutFileName: string;
const
FUNC_NAME = 'GetConsoleOuput';
begin
try
StdOutFile := 0;
AppProcess := 0;
AppThread := 0;
Result := '';
// Initialize dirs
RootDir := ExtractFilePath(ParamStr(0));
WorkDir := ExtractFilePath(CommandLine);
// Check WorkDir
if not (FileSearch(ExtractFileName(CommandLine), WorkDir) <> '') then
WorkDir := RootDir;
// Initialize output file security attributes
FillChar(SA, SizeOf(SA), #0);
SA.nLength := SizeOf(SA);
SA.lpSecurityDescriptor := nil;
SA.bInheritHandle := True;
// Create Output File
StdOutFileName := RootDir + 'output.tmp';
StdOutFile := CreateFile(PChar(StdOutFileName),
GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE,
@SA,
CREATE_ALWAYS, // Always create it
FILE_ATTRIBUTE_TEMPORARY or // Will cache in memory
// if possible
FILE_FLAG_WRITE_THROUGH,
0);
// Check Output Handle
if StdOutFile = INVALID_HANDLE_VALUE then
raise Exception.CreateFmt('Function %s() failed!' + #10#13 +
'Command line = %s', [FUNC_NAME, CommandLine]);
// Initialize Startup Info
FillChar(SI, SizeOf(SI), #0);
with SI do
begin
cb := SizeOf(SI);
dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
wShowWindow := SW_HIDE;
hStdInput := GetStdHandle(STD_INPUT_HANDLE);
hStdError := StdOutFile;
hStdOutput := StdOutFile;
end;
// Create the process
if CreateProcess(nil, PChar(CommandLine), nil, nil,
True, 0, nil,
PChar(WorkDir), SI, PI) then
begin
WaitForSingleObject(PI.hProcess, INFINITE);
AppProcess := PI.hProcess;
AppThread := PI.hThread;
end
else
raise Exception.CreateFmt('CreateProcess() in function %s() failed!'
+ #10#13 + 'Command line = %s', [FUNC_NAME, CommandLine]);
CloseHandle(StdOutFile);
StdOutFile := 0;
with TStringList.Create do
try
LoadFromFile(StdOutFileName);
Result := Text;
finally
Free;
end;
finally
// Close handles
if StdOutFile <> 0 then
CloseHandle(StdOutFile);
if AppProcess <> 0 then
CloseHandle(AppProcess);
if AppThread <> 0 then
CloseHandle(AppThread);
// Delete Output file
if FileExists(StdOutFileName) then
DeleteFile(StdOutFileName);
end;
end;
2006. szeptember 24., vasárnap
Load a bitmap and palette from a resource file
Problem/Question/Abstract:
Load a bitmap and palette from a resource file
Answer:
This routine loads a bitmap from a resource file and gets its palette and bitmap handle:
procedure LoadBitmapPalette(szTitle: PChar; var hPal: HPalette;
var hBitmap: HBitmap);
type
TMLogPalette = record
palVersion: Word;
palNumEntries: Word;
palPalEntry: array[0..255] of TPaletteEntry;
end;
var
hOldPal: HPALETTE;
pPal: TMLogPalette;
dc: hDC; { device context for palette }
hRes: THandle; { resource handle for bitmap }
pBits: PBITMAPINFOHEADER; { pointer to bitmapinfoheader
in resource }
pRgb: ^TRGBQUAD; { Zeiger auf DIB-Palettendaten }
i: integer;
Data: PChar;
begin
hRes := FindResource(hInstance, szTitle, RT_BITMAP);
if hRes <> 0 then
hRes := LoadResource(hInstance, hRes);
if hRes <> 0 then
begin
pBits := PBITMAPINFOHEADER(LockResource(hRes));
{ so: having 16 colors, we do not need a palette
(LoadBitmap is allright)
> 256 colors: no palette is needed ("hi-/direct/true color") }
if (pBits^.biBitCount <= 8) and { only for <= 256 color bitmaps }
(pBits^.biSize =
sizeof(TBITMAPINFOHEADER)) { only Windows-bitmaps, not OS/2 } then
begin
pRgb := pointer(pBits);
inc(PChar(pRgb), pBits^.biSize);
pPal.palNumEntries := 1 shl pBits^.biBitCount;
pPal.palVersion := $300;
for i := 0 to pPal.palNumEntries - 1 do
begin
pPal.palPalEntry[i].peRed := pRgb^.rgbRed;
pPal.palPalEntry[i].peGreen := pRgb^.rgbGreen;
pPal.palPalEntry[i].peBlue := pRgb^.rgbBlue;
pPal.palPalEntry[i].peFlags := 0 {PC_NOCOLLAPSE};
inc(PChar(pRgb), 4);
end;
hPal := CreatePalette(PLogPalette(@pPal)^);
DC := GetDC(0);
hOldPal := SelectPalette(DC, hPal, false);
RealizePalette(DC);
with pBits^ do
begin
biClrImportant := 0;
biClrUsed := 0;
end;
Data := pointer(pBits);
inc(Data, pBits^.biSize + pPal.palNumEntries * sizeof(TRGBQUAD));
hBitmap := CreateDIBitmap(DC, pBits^, CBM_INIT,
Data, PBitmapInfo(pBits)^,
dib_RGB_Colors);
SelectPalette(DC, hOldPal, FALSE);
ReleaseDC(0, DC);
end;
UnlockResource(hRes);
end
else
hBitmap := LoadBitmap(hInstance, szTitle);
end;
2006. szeptember 23., szombat
Change a form's caption font and alignment
Problem/Question/Abstract:
I want to change the form's caption font and alignment to DT_CENTER. How can I do this?
Answer:
Note: The formDeactivate never gets called so when the form isn't active, sometimes the FormPaint isn't called. If anything causes the form to be repainted while in inactive, it draws correctly.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
TForm1 = class(TForm)
procedure FormPaint(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure FormDeactivate(Sender: TObject);
procedure FormActivate(Sender: TObject);
private
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormPaint(Sender: TObject);
var
LabelHeight, LabelWidth, LabelTop: Integer;
caption_height, border3d_y, button_width, border_thickness: Integer;
MyCanvas: TCanvas;
CaptionBarRect: TRect;
begin
CaptionBarRect := Rect(0, 0, 0, 0);
MyCanvas := TCanvas.Create;
MyCanvas.Handle := GetWindowDC(Form1.Handle);
border3d_y := GetSystemMetrics(SM_CYEDGE);
button_width := GetSystemMetrics(SM_CXSIZE);
border_thickness := GetSystemMetrics(SM_CYSIZEFRAME);
caption_height := GetSystemMetrics(SM_CYCAPTION);
LabelWidth := Form1.Canvas.TextWidth(Form1.Caption);
LabelHeight := Form1.Canvas.TextHeight(Form1.Caption);
LabelTop := LabelHeight - (caption_height div 2);
CaptionBarRect.Left := border_thickness + border3d_y + button_width;
CaptionBarRect.Right := Form1.Width - (border_thickness + border3d_y)
- (button_width * 4);
CaptionBarRect.Top := border_thickness + border3d_y;
CaptionBarRect.Bottom := caption_height;
if Form1.Active then
MyCanvas.Brush.Color := clActiveCaption
else
MyCanvas.Brush.Color := clInActiveCaption;
MyCanvas.Brush.Style := bsSolid;
MyCanvas.FillRect(CaptionBarRect);
MyCanvas.Brush.Style := bsClear;
MyCanvas.Font.Color := clCaptionText;
MyCanvas.Font.Name := 'MS Sans Serif';
MyCanvas.Font.Style := MyCanvas.Font.Style + [fsBold];
DrawText(MyCanvas.Handle, PChar(' ' + Form1.Caption), Length(Form1.Caption) + 1,
CaptionBarRect, DT_CENTER or DT_SINGLELINE or DT_VCENTER);
MyCanvas.Free;
end;
procedure TForm1.FormResize(Sender: TObject);
begin
Form1.Paint;
end;
procedure TForm1.FormDeactivate(Sender: TObject);
begin
Form1.Paint;
end;
procedure TForm1.FormActivate(Sender: TObject);
begin
Form1.Paint;
end;
end.
2006. szeptember 22., péntek
Get a TForm's normal bounding rectangle
Problem/Question/Abstract:
I'm trying to find out where a TForm stores its original values for top, left, height and width of the form when it is maximizing. It must be stored somewhere because when I click on restore it goes to its original position and size.
Answer:
Call the GetWindowPlacement function, passing the form's Handle property as the window handle. Here's a sample function that gets a form's normal bounding rectangle:
function GetFormNormalRect(Form: TCustomForm): TRect;
var
Placement: TWindowPlacement;
begin
Placement.length := SizeOf(Placement);
if not GetWindowPlacement(Form.Handle, @Placement) then
RaiseLastWin32Error;
Result := Placement.rcNormalPosition;
end;
2006. szeptember 20., szerda
OLE Error: CoInitialize has not been called
Problem/Question/Abstract:
OLE Error: CoInitialize has not been called
Answer:
In a project that needed to display HTML documents, I decided to use the TWebBrowser control. I had used this handy ActiveX control successfully in other projects before.
This application was an MDI application, written in Delphi 5. As a 'specialty' I had installed a beta version of Internet Explorer on my system. I am not sure which of this is responsible for it, but when I would call the function in my application to display the HTML document, the TWebBrowser element could not be instantiated.
Instead I would receive an error message:
'CoInitialize has not been called'
The surprising thing is that the webbrowser control shows fine in design mode! I checked and TWebBrowser was properly installed. The underlieing DLL was also registered properly. A call of
regsvr32 shdocvw.dll
did not help. Finally I manually called the CoInitialize() function. I had to add OLE2 to the list of used units. A good place to do this is the initialization part as the sample snippet below shows.
uses
OLE2, // <-- make sure to include this unit
Windows; // and others
initialization
CoInitialize(nil); // <-- manually call CoInitialize()
end.
2006. szeptember 19., kedd
How to create a lookup table
Problem/Question/Abstract:
I have written some code for creating a table and I also want to specify a lookup table for a some fields. What's the code for that?
Answer:
var
f: TField;
i: integer;
begin
table1.FieldDefs.Update
table1.Close;
for i := 0 to table1.FieldDefs.Count - 1 do
if table1.FindField(Query.FieldDefs[i].Name) = nil then
{persistent field does not exist}
table1.FieldDefs.Items[i].CreateField(table1);
f := TStringField.Create(table1);
f.Name := 'table1lookup';
f.FieldName := 'lookup';
f.DisplayLabel := 'lookup';
f.fieldType := fklookup;
f.Calculated := True;
f.DataSet := table1;
f.lookupDataSet := table2;
f.Keyfields := 'Keyfield1';
f.Lookupfields := 'Keyfield1';
f.LookupResultField := 'ResultField';
table1.Open;
end;
2006. szeptember 18., hétfő
How to copy text from a TRichEdit to the canvas of a TImage
Problem/Question/Abstract:
How to copy text from a TRichEdit to the canvas of a TImage
Answer:
uses
Richedit;
var
imagecanvas: TCanvas;
fmt: TFormatRange;
begin
imagecanvas := image1.canvas;
with fmt do
begin
hdc := imagecanvas.handle;
hdcTarget := hdc;
rc := Rect(0, 0, imagecanvas.cliprect.right * 1440 div pixelsperinch,
imagecanvas.cliprect.bottom * 1440 div pixelsperinch);
rcPage := rc;
chrg.cpMin := 0;
chrg.cpMax := richedit1.GetTextLen;
end;
{ SetBkMode( imagecanvas.Handle, TRANSPARENT ); }
richedit1.perform(EM_FORMATRANGE, 1, integer(@fmt));
richedit1.perform(EM_FORMATRANGE, 0, 0);
image1.refresh;
end;
2006. szeptember 17., vasárnap
Streaming Components
Problem/Question/Abstract:
Saving and loading Component Published Propertys to disk
Answer:
The TStreams Class has a nice feature Couse it can stream component propertys in just one line .
So here's the complete unit where i Stream the component
If your class is not in the same unit you will have to register it .
{
procedure RegisterClasses(AClasses: array of TPersistentClass);
Description
Call RegisterClasses to register a set of custom classes in a single line. Each class is registered by calling RegisterClass. Unregistered classes can’t be loaded or saved by the VCL streaming system.
}
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
// jus a enumeration
TSomeints = 1..10;
// set of the enumeration
TExampleSet = set of TSomeints;
TExampleComponent = class(TComponent)
private
FASet: TExampleSet;
FAString: string;
FAFloat: Double;
FAInteger: Integer;
published
// Anyting you want streamed and is streameble by Delphi you can publish
// as property
property AString: string read FAString write FAString;
property ASet: TExampleSet read FASet write FASet;
property AInteger: Integer read FAInteger write FAInteger;
property AFloat: Double read FAFloat write FAFloat;
end;
TForm1 = class(TForm)
SaveToStream: TButton;
LoadFromStream: TButton;
OpenDialog1: TOpenDialog;
SaveDialog1: TSaveDialog;
EAFloat: TEdit;
EAstring: TEdit;
procedure SaveToStreamClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure LoadFromStreamClick(Sender: TObject);
private
FExampleComponent: TExampleComponent;
procedure SetExampleComponent(const Value: TExampleComponent);
{ Private declarations }
public
property ExampleComponent: TExampleComponent read FExampleComponent write
SetExampleComponent;
procedure ObjectToGui;
procedure GuiToObject;
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.SaveToStreamClick(Sender: TObject);
var
AStream: TMemoryStream;
begin
if SaveDialog1.execute then
begin
GuiToObject;
AStream := TMemoryStream.Create;
try
AStream.WriteComponent(ExampleComponent);
AStream.SaveToFile(SaveDialog1.FileName);
finally
AStream.free;
end;
end;
end;
procedure TForm1.SetExampleComponent(const Value: TExampleComponent);
begin
FExampleComponent := Value;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
// this is the Procedure To Call if you want to Use components not defined
// in this unit ;
// RegisterClasses([TButton,TMemo,TEnz]);
FExampleComponent := TExampleComponent.Create(Self);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FExampleComponent.free;
end;
procedure TForm1.LoadFromStreamClick(Sender: TObject);
var
AStream: TMemoryStream;
begin
if OpenDialog1.execute then
begin
AStream := TMemoryStream.Create;
try
AStream.LoadFromFile(OpenDialog1.FileName);
AStream.ReadComponent(ExampleComponent);
ObjectToGui;
finally
AStream.free;
end;
end;
end;
procedure TForm1.GuiToObject;
begin
ExampleComponent.AString := EAstring.Text;
ExampleComponent.AFloat := StrToFloat(EAFloat.Text);
end;
procedure TForm1.ObjectToGui;
begin
EAstring.Text := ExampleComponent.AString;
EAFloat.Text := FloatToStr(ExampleComponent.AFloat);
end;
end.
Component Download: http://www.xs4all.nl/~suusie/Pieter/Programs/StreamingComponent.zip
2006. szeptember 16., szombat
How to store the contents of a TRichEdit into a Paradox blob field while maintaining formatting
Problem/Question/Abstract:
How to store the contents of a TRichEdit into a Paradox blob field while maintaining formatting
Answer:
Table1.fieldbyName('YourBlobField').assign(richedit1.lines)
2006. szeptember 15., péntek
How to create a submenu at runtime
Problem/Question/Abstract:
I have a menu item in a menu and would like to fill a submenu with items loaded at runtime.
Answer:
procedure TForm1.Button1Click(Sender: TObject);
var
aItem, aSubItem: TMenuItem;
i, j: integer;
begin
with MenuItem1 do {is an existing MenuItem}
begin
for i := 0 to 5 do
begin
aItem := TMenuItem.Create(MainMenu1);
Add(aItem);
aItem.Caption := IntToStr(i);
for j := 0 to 5 do
begin
aSubItem := TMenuItem.Create(MainMenu1);
aItem.Add(aSubItem);
aSubItem.Caption := IntToStr(j);
aSubItem.OnClick := MenuItemsHandler;
end;
end;
end;
end;
procedure TForm1.MenuItemsHandler(Sender: TObject);
begin
ShowMessage((Sender as TMenuItem).Caption);
end;
2006. szeptember 14., csütörtök
Adding an url to Browser/Windows Favorite
Problem/Question/Abstract:
How to add an url to Browser/Windows Favorite
Answer:
Note. I've made this example for complete beginner.
Jump to the code if you don't need any explanation. There no API manipulation here, we place manually the shortcut in the the folder.
Here's how you can add an url in you favorite. This work for any browser, except if they use special data type to store their url library. The only example I see of an application that use another type of data is the utility LinkMan wich is, anyway, not a browser application.
Basicly, to create a shortcut url all you need to know is the structure of this file type. You need also to know a little about file manipulation, if you don't, this example will serve also as a guide for very simple file manipulation.
First put 3 TEdit component on your form and add TButton component.
Add three label to identify you EditBox. First EditBox should be associate with Folder, the second with URL and the last with Title.
Folder - Will store the access path to your browser favorite folder
URL - Will store the URL (Http, www, etc)
Title - Is the name you give to the URL (Yahoo, Baltsoft)
For the AddUrl procedure to work, you need to pass it these three paramater as string. To do so affect your .Text property of your TEdit Component to your own variables.
If you are new to Delphi you will find pretty interesting the output to file part where I use AssignFile, ReWrite, WriteLn... I suggest you try some of you own app using those command to test results with your own value.
I included also a small line that can create a folder if the folder don't exist. This is a proper to the use of FileCtrl. Also of interest the small handling of \ if it's not present for lazy user.
Don't change you components name, it will be easier to follow the code.
Here we go..
unit MyUnit;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls,
FileCtrl;
type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
procedure Button1Click(Sender: TObject);
procedure AddUrl(Folder, Url, Title: string);
end;
var
Form1: TForm1;
MyFolder: string;
MyUrl: string;
MyTitle: string;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
begin
MyFolder := Edit1.Text;
MyURL := Edit2.Text;
MyTitle := Edit3.Text;
AddUrl(MyFolder, MyUrl, MyTiTle);
end;
procedure TForm1.AddURL(Folder, Url, Title: string);
var
MyUrlFile: TextFile;
begin
if Folder[Length(Folder)] <> '\' then
Folder := Folder + '\';
if not DirectoryExists(Folder) then
ForceDirectories(Folder);
try
AssignFile(MyUrlFile, Folder + title + '.url');
Rewrite(MyUrlFile);
WriteLn(MyUrlFile, '[InternetShortcut]');
WriteLn(MyUrlFile, 'URL=' + url);
finally
Closefile(MyUrlFile);
end;
end;
end.
2006. szeptember 13., szerda
Change the position of a list item in a TListView
Problem/Question/Abstract:
How can I move a list item of a TListView one postion down or up?
Answer:
var
item: TListItem;
begin
with lvlist do
begin
{preserve item since currentPos will be invalid after next line}
item := items[currentPos];
{make a new item and assign to it}
items.insert(moveToPos).assign(item);
{old item will now remove itself from the listview}
item.delete;
end;
end;
2006. szeptember 12., kedd
Check if the BDE is installed
Problem/Question/Abstract:
I want to run the BDE install from my own setup application. Before I run the BDE installation, I would like to check that BDE is installed.
Answer:
Solve 1:
function isbdepresent: boolean;
var
IdapiPath: array[0..255] of Char;
IdapiHandle: THandle;
begin
result := false;
GetProfileString('IDAPI', 'DLLPath', 'C:\', IdapiPath, 255);
{next lines isolates the first directory path from the IdapiPath in case
there are more}
if Pos(';', StrPas(IdapiPath)) <> 0 then
begin
StrPCopy(IdapiPath, Copy(StrPas(IdapiPath), 1, Pred(Pos(';',
StrPas(IdapiPath)))));
end;
IdapiHandle := LoadLibrary(StrCat(IdapiPath, '\IDAPI01.DLL'));
if IdapiHandle < HINSTANCE_ERROR then
result := false
{IDAPI is not present on this system}
else
begin
FreeLibrary(IdapiHandle);
result := true;
{IDAPI is present on this system}
end;
end;
Solve 2:
Try to check the registry for the presence of the BDE:
with TRegistry.create do
begin
Rootkey := HKEY_LOCAL_MACHINE;
OpenKey('SOFTWARE\BORLAND\DATABASE ENGINE', false);
CFGFile := ReadString('CONFIGFILE01');
Free;
end;
Solve 3:
you can try to initialize the BDE
IsBDEExist := (dbiInit(nil) = 0)
2006. szeptember 11., hétfő
How to change the button caption in a TOpenDialog
Problem/Question/Abstract:
I need a dialog for the user to select files to delete. The TOpenDialog works just fine except that the button says Open, and I need it to say Delete.
Answer:
You will need to include Commdlg in your uses clause. In the TOpenDialog.OnShow event:
procedure TForm1.OpenDialog1Show(Sender: TObject);
begin
SendMessage(GetParent(OpenDialog1.Handle), CDM_SETCONTROLTEXT, 1,
Integer(PChar('Delete')));
end;
2006. szeptember 10., vasárnap
TCheckListBox LoadFromFile/SaveToFile Method, included checked state?
Problem/Question/Abstract:
What to use the TCheckListBox LoadFromFile and SaveToFile Method, and store the checked state at the same time?
Answer:
If you embed the Checked property into the actual entry as a “1” or “0” charter you can save the file with the normal SaveToFile method. When a file is loaded using the LoadFromFile method as normal. Then extract the first charter from the entry and you will have the checked state.
{====================================}
procedure TFrameRuleEngine.SaveRules;
{====================================}
var
i: Integer;
begin
i := 0;
while i < CheckListBoxRule.Items.Count do
begin
if CheckListBoxRule.Items[i] = '' then
begin
// Delete entry it is empty
CheckListBoxRule.Items.Delete(i);
end
else
begin
// Add a 1 or 0 as the first charter in the entry for checked or not checked
CheckListBoxRule.Items[i] := IntToStr(Integer(CheckListBoxRule.Checked[i])) +
CheckListBoxRule.Items[i];
Inc(i);
end;
end;
// Save the full list as normal
CheckListBoxRule.Items.SaveToFile(ExtractFilePath(Application.ExeName) +
'Rule.Txt');
end;
{===================================}
procedure TFrameRuleEngine.LoadRules;
{===================================}
var
sChecked: string;
i: Integer;
begin
if FileExists(ExtractFilePath(Application.ExeName) + 'Rule.Txt') then
begin
// Read the file as normal
CheckListBoxRule.Items.LoadFromFile(ExtractFilePath(Application.ExeName) +
'Rule.Txt');
i := 0;
while i < CheckListBoxRule.Items.Count do
begin
if CheckListBoxRule.Items[i] = '' then
begin
// Delete an empty entry
CheckListBoxRule.Items.Delete(i);
end
else
begin
// Get the checked state
sChecked := Copy(CheckListBoxRule.Items[i], 1, 1);
CheckListBoxRule.Items[i] := Copy(CheckListBoxRule.Items[i], 2,
Length(CheckListBoxRule.Items[i]));
// Update the Checked property
CheckListBoxRule.Checked[i] := Boolean(StrToInt(sChecked));
Inc(i);
end;
end;
end;
end;
2006. szeptember 9., szombat
Reporting file size in a descriptive string
Problem/Question/Abstract:
How to tell the file size in a descriptive string like "2.33MB", or "233 bytes", as Windows Explorer does in a status bar?
Answer:
The articles of Adam Lanzafame and NYB about file size string, and follow-up discussions inspired me to come with a solution which would resolve some of the issues of both aforementioned approaches/implementations (see the DPFileSize unit attached below).
Some of these issues are:
Adam's function depends on the external library presence, specifically SHLWAPI.DLL (Shell Light-weight Utility Library), which is essentially a helper library and may not be installed on all systems.
For files from 1000 to 1023 bytes in size both Adam's and NYB's functions return the size in bytes, while Windows Explorer displays '0.99KB'
Windows Explorer doesn't add space between a number and 'KB', 'MB', and 'GB', while both Adam's and NYB's functions do.
NYB's function always displays two digits after a decimal point (even for bytes), while the main idea of how Windows Explorer represents the number here is obviously to keep three significant digits, including leading zero before decimal point (see item 2 above)
NYB's function rounds the resulting value to the nearest value of the least significant digit. The Windows Explorer approach seems to me as more consistent - we may accept either one of the rounding directions, but it is better to be fixed. The direction used by Explorer is towards lesser value of the least significant digit, so it consistently shows that a file is at least of indicated size.
All these issues have been addressed in the unit below.
//******************************************************************************
//
// Unit Name: DPFileSize
// Purpose : Functions for reporting file size with a descriptive string
// Author : (c) 2001 Dmitri Papichev {Dmitri.Papichev@iname.com}
// Comments : Specially for www.delphi3000.com
//
//******************************************************************************
unit DPFileSize;
{==============================================================================}
interface
const
KB = 1024;
MB = KB * KB;
GB = MB * KB;
{main function}
function GetFileSizeString(const AFileName: string): string;
{helper functions, surfaced here as they might be used on their own}
function DPGetFileSize(const AFileName: string): integer;
function GetSignificantDigits(const ARealNumber: double;
const ADigits: integer): string;
function FormatFileSizeValue(const AValue: integer): string;
{==============================================================================}
implementation
uses
SysUtils,
Classes;
{------------------------------------------------------------------------------}
{returns the string representing the file size for a given filename, in a way
similar to what Windows Explorer does}
function GetFileSizeString(const AFileName: string): string;
begin
try
Result := FormatFileSizeValue(DPGetFileSize(AFileName));
except
on E: Exception do
begin
Result := E.Message;
end; {on}
end; {if}
end; {--GetFileSizeString--}
{------------------------------------------------------------------------------}
{returns file size in bytes for a given filename}
function DPGetFileSize(const AFileName: string): integer;
var
AFileStream: TFileStream;
begin
AFileStream := TFileStream.Create(AFileName,
fmShareCompat or fmShareDenyNone);
try
Result := AFileStream.Size;
finally
AFileStream.Free;
end; {try}
end; {--GetFileSizeDP--}
{------------------------------------------------------------------------------}
{returns first ADigits significant digits of ARealNumber,
with a decimal point if any}
function GetSignificantDigits(const ARealNumber: double;
const ADigits: integer): string;
begin
if ADigits in [1..16] then
begin {that's the range of sig. digits supported}
Result := Format('%' +
IntToStr(ADigits) + '.' +
IntToStr(ADigits) + 'f', [ARealNumber]);
Result := Copy(Result, 1, ADigits + 1);
if (Pos('.', Result) in [0, ADigits + 1]) then
begin
Result := Copy(Result, 1, ADigits);
end; {if}
end
else
begin
raise Exception.Create('GetSignificantDigits: ' +
'A number of significant digits out of range');
end; {if}
end; {--GetSignificantDigits--}
{------------------------------------------------------------------------}
{converts given AValue to the string representing file size, in a way similar
to what Windows Explorer does}
function FormatFileSizeValue(const AValue: integer): string;
begin
case AValue of
0..999:
begin
Result := IntToStr(AValue) + ' bytes';
end;
1000..(MB - 1):
begin
Result := GetSignificantDigits(AValue / KB, 3) + 'KB';
end;
MB..(GB - 1):
begin
Result := GetSignificantDigits(AValue / MB, 3) + 'MB';
end;
else
begin
Result := GetSignificantDigits(AValue / GB, 3) + 'GB';
end;
end; {case}
end; {--FormatFileSizeValue--}
end.
2006. szeptember 8., péntek
Change the primary mouse button
Problem/Question/Abstract:
How do you change the primary mouse button
Answer:
To change the primary mouse button in code you need to execute an API function called SwapMouseButton. This changes the primary button but does not alert the control panel applet for the mouse that the primary button has changed. To do this we need to write to the registry. The code below shows how to toggle the primary mouse button by first reading the registry to determine the current assignment then does the toggle by writing to the registry and executing the SwapMouseButton function.
uses Windows, Registry;
const
LeftButton = '0';
RightButton = '1';
VaueToRead = 'SwapMouseButtons';
begin
with TRegistry.Create do
begin
try
if OpenKey('Control Panel\Mouse', False) then
begin
if ValueExists(VaueToRead) then
if ReadString(VaueToRead) = LeftButton then
begin
SwapMouseButton(True);
WriteString(VaueToRead, RightButton);
end
else
begin
SwapMouseButton(False);
WriteString(VaueToRead, LeftButton);
end;
CloseKey;
end;
finally
Free;
end;
end;
end.
2006. szeptember 7., csütörtök
How to change the highlight colour in a TDBGrid
Problem/Question/Abstract:
How to change the highlight colour in a TDBGrid
Answer:
procedure TForm1.DBGrid1DrawDataCell(Sender: TObject; const Rect: TRect;
Field: TField; State: TGridDrawState);
begin
if gdSelected in State then
DBGrid1.Canvas.Brush.Color := clLime;
DBGrid1.DefaultDrawDataCell(Rect, Field, State);
end;
or use the OnDrawColumnCell method like this:
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
begin
inherited;
with (Sender as TDBGrid) do
begin
if gdSelected in State then
begin
Canvas.Font.Style := [fsBold];
Canvas.Font.Color := clRed;
Canvas.Brush.Color := clYellow;
DefaultDrawColumnCell(Rect, DataCol, Column, State);
end;
end;
end;
2006. szeptember 6., szerda
How to register an OCX
Problem/Question/Abstract:
How to register an OCX
Answer:
Your installation program needs to register an OCX, but doesn't support this? Or you want to
register it by your program yourself?
Suppose the OCX you want to use is called
program RegisterMyOCX;
uses
OLECtl, Windows, Dialogs;
var
OCXHand: THandle;
RegFunc: TDllRegisterServer; //add to the uses clause
begin
OCXHand := LoadLibrary('c:\windows\system\test.ocx');
RegFunc := GetProcAddress(OCXHand, 'DllRegisterServer'); //case sensitive
if RegFunc <> 0 then
RegFunc
else
ShowMessage('Error!');
FreeLibrary(OCXHand);
// You can the same way unregister the OCX:
// replace 'DllRegisterServer' by 'DllUnregisterServer'
end.
2006. szeptember 5., kedd
Display a sort order indicator in the column header of a TListView (2)
Problem/Question/Abstract:
Does anyone know how to add custom painting to the column headings in vsReport mode (short of ownerdrawing everything)? I'd like to add indication of sort order and more. I don't think the columns ImageIndex is a satisfactory solution. I would like the image of the sort indicator on the right.
Answer:
The problem is that not all versions of the listview common control support this. You have to drop to the API to make use of it. This is somewhat ackward (the common controls seem to get more cumbersome to use with each version). The listviews header line is an actual header control. A header control can display either images from an imagelist or a bitmap. Only the bitmap can be arranged to the right of the caption text. The listview offers no direct method to set a bitmap for a header, so you have to get the header controls handle and send messages to it directly. The bitmap you use should be created on form creation and destroyed on form destruction.
The following example shows the principle. There is a major snag here, though. Since the VCL listview has no idea that you changed some header properties it will happily wipe out what you did every time it feels like resetting some of the header properties, e.g. when the user resizes one of the columns. This can be dealt with if needs be, by subclassing the header control to trap the HDM_SETITEM messages that change the item properties. The TListview class already subclasses the header but the method used is private and not virtual, so not accessible.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ComCtrls, StdCtrls, ImgList;
type
TForm1 = class(TForm)
ListView1: TListView;
ImageList1: TImageList;
procedure ListView1ColumnClick(Sender: TObject; Column: TListColumn);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
FUpArrow, FDownArrow: TBitmap;
procedure SetColumnSortOrder(lv: TListview; Column: TListcolumn);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
commctrl;
{$R *.DFM}
procedure TForm1.SetColumnSortOrder(lv: TListview; Column: TListcolumn);
var
hdr: HWND;
hdritem: THDItem;
begin
hdr := Listview_GetHeader(lv.handle);
FillChar(hdritem, sizeof(hdritem), 0);
hdritem.Mask := HDI_FORMAT;
Header_GetItem(hdr, column.index, hdritem);
hdritem.Mask := HDI_FORMAT or HDI_BITMAP;
if column.tag = 0 then
hdritem.hbm := FUpArrow.Handle
else
hdritem.hbm := FDownArrow.Handle;
hdritem.fmt := hdritem.fmt or HDF_BITMAP_ON_RIGHT or HDF_BITMAP;
Header_SetItem(hdr, column.index, hdritem);
end;
procedure TForm1.ListView1ColumnClick(Sender: TObject; Column: TListColumn);
begin
Column.Tag := Ord(not Odd(Column.Tag));
SetColumnSortOrder(Sender as TListview, Column);
end;
procedure TForm1.FormCreate(Sender: TObject);
procedure MakeBitmap(var bmp: TBitmap; imageindex: Integer);
begin
bmp := TBitmap.Create;
bmp.Width := imagelist1.width;
bmp.Height := imagelist1.height;
with bmp.Canvas do
begin
Brush.Color := clBtnface;
Brush.Style := bsSolid;
FillRect(Cliprect);
end;
imagelist1.Draw(bmp.canvas, 0, 0, imageindex);
end;
begin
MakeBitmap(FUpArrow, 1);
MakeBitmap(FDownArrow, 0);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FUpArrow.Free;
FDownArrow.Free;
end;
end.
2006. szeptember 4., hétfő
Change the alignment for TEdit
Problem/Question/Abstract:
How can I change the alignment for my TEdit?
Answer:
Sometimes you need change the text alignment in standard TEdit component. For some reason the developers in Microsoft decided, that for data editing in single line we do not need to change alignment and haven't provided such possibility:(
But sometimes I need it! For example, I like view a numbers with right alignment...
If you need it too then this delphi tip for you:
type
TEditAlignment = class(TCustomEdit)
protected
{ Protected declarations }
procedure CreateParams(var Params: TCreateParams); override;
end;
procedure TEditAlignment.CreateParams(var Params: TCreateParams);
const
Alignments: array[TAlignment] of Longint =
(ES_LEFT, ES_RIGHT, ES_CENTER);
begin
inherited CreateParams(Params);
Params.Style := Params.Style or ES_MULTILINE or
Alignments[FAlignment];
end;
In Windows 98 you can set a Params.Style without ES_MULTILINE flag and it too will work.
Also after such edit control can't correctly work with PasswordChar <> #0 (but I think for password input it's not necessary to change alignment).
PS: remark, that after that your TEdit is not "real" edit control - now is a control like "memo" but single line... Of course, you can use a standard TMemo component with height equal to one line.
Component Download: http://www.geocities.com/mshkolnik/download/edittype.zip
2006. szeptember 3., vasárnap
How to set a string or integer property for a component if it exists
Problem/Question/Abstract:
I am building a routine that checks our forms for validity before deploying them. I would like to use some kind of structure that tests if a component type has access to a certain property, something like: " if (self.Controls[b] has Tag) then ...". Can anyone offer suggestions?
Answer:
Here's an example of setting a string property for a component if it exists and another for an integer property:
procedure SetStringPropertyIfExists(AComp: TComponent; APropName: string;
AValue: string);
var
PropInfo: PPropInfo;
TK: TTypeKind;
begin
PropInfo := GetPropInfo(AComp.ClassInfo, APropName);
if PropInfo <> nil then
begin
TK := PropInfo^.PropType^.Kind;
if (TK = tkString) or (TK = tkLString) or (TK = tkWString) then
SetStrProp(AComp, PropInfo, AValue);
end;
end;
procedure SetIntegerPropertyIfExists(AComp: TComponent; APropName: string;
AValue: Integer);
var
PropInfo: PPropInfo;
begin
PropInfo := GetPropInfo(AComp.ClassInfo, APropName);
if PropInfo <> nil then
begin
if PropInfo^.PropType^.Kind = tkInteger then
SetOrdProp(AComp, PropInfo, AValue);
end;
end;
2006. szeptember 2., szombat
How to really add a form to a DLL
Problem/Question/Abstract:
Adding a form to a DLL is actually quite simple. Depending on the type of form (modal or mode-less) you have to proceed differently. This article explains how it is done.
Answer:
To add a form to a DLL you have to remember these things:
assign the calling programs Application.Handle to the DLL's Application.Handle
write one or two exported functions that handles the interaction with the calling program
include sharemem as the first unit in the DLL's uses clause if the exported functions uses strings
if you are going to show a mode-less form, you should return a handle to your form in the "Show" function and require it as a parameter in the "Close" function
always create the form with the Application object as the Owner
restore the DLL's Application.Handle after closing the form
You don't have to do anything special to add a form to a DLL: just add the forms filename to the uses clause and Delphi will compile it right into the DLL.
Here's an example with both modal and mode-less invocation. The examples just return an integer value, but you could of course return just about anything:
library testDLL;
uses
myTestFrom, SysUtils, Controls;
var
OldApphandle: longint = 0;
{ these functions are used with the mode-less form: }
{ AppHandle is the *calling* applications Handle }
function ShowTestForm(AppHandle: integer): longint;
var
F: TmyTestForm;
begin
{ save current handle unless it's already done }
if Application.Handle <> AppHandle then
OldAppHandle := Application.Handle;
{ assign new }
Application.Handle := AppHandle;
{ create and show form }
F := TmyTestForm.Create(Application);
F.Show;
Result := longint(F);
end;
{ the input value, Handle, must be the same value as returned by ShowTestForm }
function CloseTestForm(Handle: longint): integer;
var
F: TmyTestForm;
begin
{ typecast back to TForm (some sanity checks here would not be bad...}
F := TmyTestForm(Handle);
Result := F.SomeIntValue;
F.Close;
F.Free;
{ restore previous handle }
Application.Handle := OldAppHandle;
end;
{ this function is used to show the form modally }
function ShowTestFormModal(AppHandle: integer): longint;
var
F: TmyTestForm;
begin
OldAppHandle := Application.Handle;
try
Application.Handle := AppHandle;
F := TmyTestForm.Create(Application);
try
if F.ShowModal = mrOK then
Result := F.SomeIntValue
else
Result := -1;
finally
F.Free;
end;
finally
Application.Handle := OldAppHandle;
end;
end;
{ finally export the functions: }
exports ShowTestForm name 'ShowTestForm', CloseTestForm name 'CloseTestForm',
ShowTestFormModal name 'ShowTestFormModal';
begin
end.
2006. szeptember 1., péntek
How to disable a TTimer while browsing a menu
Problem/Question/Abstract:
I have an application that auto-minimizes itself after 4 seconds, when maximized (using TTimer). The main form has a menu. I want the Timer to be disabled while the user browses the menu items. Is that possible?
Answer:
Solve 1:
Add this line to your form declaration:
protected
procedure WMMenuSelect(var msg: TWMMenuSelect); message WM_MenuSelect;
and then add this procedure:
procedure TForm1.WMMenuSelect(var msg: TWMMenuSelect);
begin
tmrAutoClose.Enabled := (msg.MenuFlag = $FFFF);
end;
Solve 2:
Yes. The form will get one special message when the menu is first opened (WM_ENTERMENULOOP) and a second when the menu is finally closed (WM_EXITMENULOOP). Add handlers for these and disable/ enable the timer in them.
{ ... }
private
procedure WMEnterMenuLoop(var msg: TMessage);
message WM_ENTERMENULOOP;
procedure WMExitMenuLoop(var msg: TMessage);
message WM_EXITMENULOOP;
{ ... }
procedure TForm1.WMEnterMenuLoop(var msg: TMessage);
begin
IdleTimer.Enabled := false;
inherited;
end;
procedure TForm1.WMExitMenuLoop(var msg: TMessage);
begin
IdleTimer.Enabled := true;
inherited;
end;
Feliratkozás:
Bejegyzések (Atom)