Is there a method or compiler directive or some way of assuring certain components, such as queries or database connections get set to active=false or disconnected when you run a build/compile? Seems so often these are turned on by something else and you don't notice it until its too late.
My particular install is Delphi 7
The Set Component Properties feature of GExperts is able to do that.
i think the best option would be to subclass stock connection component and in your own one override .Loaded method like that
if not csDesigning in Self.ComponentState then
if not Self.ActiveInDFM {new boolean published property} then
if Self.Active then Self.Active := false;
inherited;
http://docwiki.embarcadero.com/Libraries/XE3/en/System.Classes.TComponentState
http://docwiki.embarcadero.com/Libraries/XE3/en/System.Classes.TComponent.Loaded
By (ab)using Delphi Form Designer stupidness you can use it even without actually installing your new component into IDE Palette - just give it the same name as to the stock component class, then put your own method as last in the form's interface-uses list: thus in design-time you would have stock component and when compiling it would be transparently substituted with your own one.
Or you can sub-class it right above the very form declaration like (for another component):
type
TPanel = class(ExtCtrls.TPanel)
private
...
TForm1 = class(TForm) ....
I guess this approach might be seen as analogue to aspect-oriented programming, using limitations of IDE in developer-benefitting way.
Another approach might be some script, that cleans .Active properties in DFM on save or before build, but this way is complex for
i may be harder to integrate with stand-alone build severs (new script for each different CI framework tried)
it would reset Active property for design-time as well. This is a proper thing to do, from rigorous point of view. Yet this might be not very convenient.
You may just use similar code in your Form's and DataModule's .Loaded method (you would have to override it instead connection's method then).
You can copy-paste the same code into every Form's Loaded method.
procedure TMyForm.Loaded; // override
var c: TComponent; i: integer;
begin
try
for i := 0 to Self.ComponentsCount - 1 do begin
c := Self.Components[i];
if c is TCustomConnection then
with TCustomConnection(c) do // Hate those redundant typecasts!
if Connected then Connected := false;
if c is TDataSet then
with TDataSet(c) do // Delphi could took a lesson from Component Pascal
if Active then Active := false;
if c is ... // transactions, stored procedures, custom libriaries...
end;
finally
inherited;
end;
end;
This seems to be less sly way - thus most reliable. Yet that is a lot if copy-paste, and if you would later add some new component or library, that may ask for modifying copy-pasted code in all the forms.
You may centralize this code in some MyDBUtils unit into global procedure like Disconnect(const owner: TComponent); and then
procedure TMyForm.Loaded; // override
var c: TComponent; i: integer;
begin
try
MyDBUtils.Disconnect(Self);
finally
inherited;
end;
end;
This approach also has drawbacks though:
This would make MyDBUtils unit tightly coupled with all and every the database-related libs and components you might use. For large inherited projects, consisting of different binary modules, historically based on different db-access libraries and in process of migration, thus pulling all the access libraries into every binary module.
It can be overcome by ad hoc DI framework, but then the opposite can happen: you risk under-delivering, you may just forget to inject some library or component handler into the project that actually use it or got modified to use it.
If your form would have some components, whose connectivity should NOT be reset (object arrays as datasets, in-memory tables, in-memory NexusDB or SQLite databases, etc), you'd have to come up with ad hoc non-obvious convention to opt them out.
In my applications, I set my connection's Tag property to 1 at design time. In the OnBeforeConnect event, I check Tag, and if it is equal to 1, I abort the connection and set it to 0.
Related
I am developing a Delphi 10.1 VCL application for Windows.
For integer or float input I need a number input field which is connected with a slider. When the user changes the number in the input field the slider position changes accordingly. When the user changes the slider position the number in the number field is updated.
I can solve this by using a TEdit and a TTrackBar and add the necessary update functionality in their OnChange event handlers.
The problem is that I need many of such inputs on different forms. Therefore I would like to create a new component which combines the two controls TEdit and TTrackBar in one component.
Is the creation of a new component the best strategy for the multiple use of such a slider input?
What is the best way to create such a new component?
Is the creation of a new component the best strategy for the multiple
use of such a slider input?
Not necessarily true all the time. (by my standards at least).
What is the best way to create such a new component?
I know three ways to solve your problem.
Way number 1:
create the component using the new component wizard where you create dynamically the TEdit and the TTrackBar sub components in a TGroupBox descendant.
the following is how I would do that.
unit Combindedittrack;
interface
uses
System.SysUtils,
System.Classes,
Vcl.Controls,
Vcl.comctrls,
Vcl.StdCtrls;
type
TCombindEditTrack = class(TGroupBox)
private
{ Private declarations }
FEdit: TEdit;
FTrackBar: TTrackBar;
procedure EditOnChangeProc(Sender: TObject);
procedure TrackBaroOnChangeProc(Sender: TObject);
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TCombindEditTrack]);
end;
constructor TCombindEditTrack.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
SetBounds(0, 0, 250, 50);
FEdit := TEdit.Create(Self);
with FEdit do
begin
Text := ''; //<-- you control the appearence here
Top := 10;
Left := 10;
Height := 27;
Width := 50;
Parent := Self;
OnChange := EditOnChangeProc; // your Onchange event handler for the Tedit
end;
FTrackBar := TTrackBar.Create(self);
with FTrackBar do
begin
Top := 10; //<-- you control the appearence here
Left := 60;
Height := 30;
Width := 50;
Parent := self;
Onchange := TrackBaroOnChangeProc; // your Onchange event handler for the Ttrackbar
end;
end;
destructor TCombindEditTrack.Destroy;
begin
FTrackBar.Free;
FEdit.Free;
inherited;
end;
procedure TCombindEditTrack.TrackBaroOnChangeProc(Sender: TObject);
begin
// <-- track bar onchange handling here.
end;
procedure TCombindEditTrack.EditOnChangeProc(Sender: TObject);
begin
// <-- edit onchange handling here.
end;
end.
Way number 2:
Use frames like this (I'm on delphi 10 seattle).
File-->New-->Other-->(search for frames on delphi files).
Now add the edit and the track bar and set their Onchange events.
Save the unit.
on the tool palette (the standard component section) click on the frame component.
choose the frame you just created.
You will have a replica of the frame each time you use it.
Way number 3:
Use component template like this (again I'm on delphi 10 seattle)
Select your already created and modified tedit and ttrackbar.
On the toolbar's "component" click "create component template".
Name your template and press OK.
Select the Template palette and then your template.
Now notice that even your code (events) are added as well to your project.
Finally
With the level I have on delphi and the IDE I'm really unable to give you a clear answer to which is the best way but nevertheless I have shared all what I know that could help you.
edit: Since a lot of comments are insisting that the answer should state which is the best way to do this. this is the best way based on the following.
let's put some of the key point that should be accounted for when choosing
1. Ease of modifying the combined control(s) if you wish so (by my experience you will).
2. time needed to complete this task (it means the time it will take
you to fully complete the task with minimum debugging and coding).
3. general source code readability.
4. usefulness for the future of your projects.
Now lets start criticizing the three methods based on the those criteria.
Way number 1:
C1(criteria number 1): Just modify the the source implementation of the component and each replica/use will have the same effects and properties. However this is not the case for way number 3.
C2: It depends on your knowledge of component writing but for this component it took me 5 min to create it and I'm only a beginner in delphi. For the debugging if something went wrong and the problem is in the component implementation than you just need to fix once (see C1)
C3: their is no implementation in your form(s) source code for your component just add it to your form and every thing is hidden (for example add a tedit and go to see the implementation in your forms source).
C4: You are creating a component after all this will open the door for you to create your own set of components like the Flatstyle or Indy open source components. so next time you need some thing like this you just drop it in your form designer and you are done.
Way number 2: frames
C1: It is like way number 1 because you are creating a component but it is visually this time. modifying the source frame will change the effects and properties of the replicas, also you can add extra handling to your replicas.
the event handler of the replica's Onchange event is like this
procedure TForm1.Frame2Edit1Change(Sender: TObject);
begin
Frame2.Edit1Change(Sender); //<-- this is the original onchange event you setup at the beginning
//<-- you can extra handling here if you want one of the replicas to behave differently than the original
end;
C2: same time and maybe faster than way number 1.
C3: over all, it has the same out come as way number 1.
C4: unlike way number 1 you can not use frames created in project A in project B. So your coding and debugging will stay in project A.
Way number 3: component template.
C1: you are not creating a component you are creating a repleca/macro of the exact steps you did in your last project. changing one will not change the others they are separated.
C2: same time and maybe faster than way number 1.
C3: each time you add a template to your form the events code will be added (not a good view if it is a long Onchange code).
C4: You can use templates created in project A in project B. However what you wrote in project A will be in project B (see c1) even the references of variables that don't exist in project B (this can be hard to debug and misleading, considering the period of time between each use of the template).
Conclusion: each of the ways presented will consume time to code and debug and all of them will do the task, How ever for the sake of simplicity and the reuse with minimum risks Way number 1 is the safe choice here because it will give you the chance to update and upgrade safely. also debug faster.
the good thing also about way number 1 is that after a while when you will forget the implementation and how things are working internally. The only thing that should stay in mind is the purpose of the component because it will become one of the various component you use (you don't know how Tedit is implemented and you don't need to but yet you use it in every single project you create).
based on the criteria given Way number 1 is the best.
Maybe using a container control that contains both controls is a simpler alternative. I am using ccpack for this.
https://sourceforge.net/projects/ccpack/
Custom Containers Pack (CCPack) is an integrated tool and component mini-
library to produce and maintain composite controls (or simply “composites”)
and other containers (forms, data modules and frames). The process of
building composite components looks like ActiveForm and Frame creating, but
the result is the native VCL component. You can create new composites just
as usual forms.
You can create a Frame and then register that Frame as a component. The end result is very similar to creating a code only component where the sub components are created in the constructor (Nasreddine's number 1 option). However this method allows you to visually design the component and use the object inspector to create your event handlers.
Here is a Stack Overflow question that shows how to register the frame:
How to Improve the Use of Delphi Frames
Is it possible to put some classes into a DLL?
I have several custom classes in a project I am working on and would like to have them put in a DLL and then accessed in the main application when needed, plus if they are in a DLL I can reuse these classes in other projects if I need to.
I found this link: http://www.delphipages.com/forum/showthread.php?t=84394 which discusses accessing classes in a DLL and it mentions delegating to a class-type property but I could not find any further information on this in the Delphi help or online.
Is there any reason I should not put classes in a DLL, and if it is ok is there a better way of doing it then in the example from the link above?
Thanks
It is not possible to get a Class/Instance from a DLL.
Instead of the class you can hand over an interface to the class.
Below you find a simple example
// The Interface-Deklaration for Main and DLL
unit StringFunctions_IntfU;
interface
type
IStringFunctions = interface
['{240B567B-E619-48E4-8CDA-F6A722F44A71}']
function CopyStr( const AStr : WideString; Index, Count : Integer ) : WideString;
end;
implementation
end.
The simple DLL
library StringFunctions;
uses
StringFunctions_IntfU; // use Interface-Deklaration
{$R *.res}
type
TStringFunctions = class( TInterfacedObject, IStringFunctions )
protected
function CopyStr( const AStr : WideString; Index : Integer; Count : Integer ) : WideString;
end;
{ TStringFunctions }
function TStringFunctions.CopyStr( const AStr : WideString; Index, Count : Integer ) : WideString;
begin
Result := Copy( AStr, Index, Count );
end;
function GetStringFunctions : IStringFunctions; stdcall; export;
begin
Result := TStringFunctions.Create;
end;
exports
GetStringFunctions;
begin
end.
And now the simple Main Program
uses
StringFunctions_IntfU; // use Interface-Deklaration
// Static link to external function
function GetStringFunctions : IStringFunctions; stdcall; external 'StringFunctions.dll' name 'GetStringFunctions';
procedure TMainView.Button1Click( Sender : TObject );
begin
Label1.Caption := GetStringFunctions.CopyStr( Edit1.Text, 1, 5 );
end;
Use runtime packages for this purpose; it's exactly what they're designed for in the first place. They get loaded automatically (or can be loaded manually), and automatically set up the sharing of the same memory manager so you can freely use classes and types between them.
You're much better off using packages (which is exactly what the IDE does for much of its functionality for that very reason).
Delphi does not support either importing or exporting classes from DLLs. To import a class from another module, you need to use packages.
While the official answer is "you can't", anything is possible of course. Frameworks like Remobjects SDK and Remobjects Hydra has been doing this for a long time. The problem is that it requires you to create an infrastructure around such a system, which is not something Delphi deals with out of the box.
The first step is memory management. A DLL is injected into the process loading it, but it does not share memory management. It has to be this way since a DLL can be created in a myriad of languages, each with their own internal mechanisms. This poses a problem with safety (i.e program writing into DLL memory and visa versa).
Secondly, interface (read: content description). How will your application know what classes it can create, class members, parameter types and so on. This is why COM requires type-libraries, which describe the content of a DLL.
Third, life-time management. If memory management for the objects created from a DLL is handled by the DLL, the DLL must also release said objects.
The above steps already exists and it's called COM. You are of course free to create as many COM DLL files as you please, just remember that these have to be registered with Windows before you use them. Either "on the fly" by your application (if you have the security rights to do so) or by your installer.
This is why COM, while representing the ultimate plugin system, is rarely used by Delphi programmers, because the technical cost of using it as a plugin system outweighs the benefits.
The alternative way
If you can assure that your DLL's are only to be used by Delphi programs, then you have a second way to explore. You have to create methods to share the memory manager of your main program with the DLL (Remobjects does this). This allows you to share objects, strings and more between the DLL and the main application.
You can then use RTTI to "map" classes stored in the DLL (the DLL must do this and generate both class and method tables) which can be invoked through a proxy class you device yourself.
All in all, unless you have plenty of free time to waste, I would either buy a system like Remobjects Hydra - or stick with packages. But can it be done another way? Of course it can. But at the cost of time and hard work.
I've got a Delphi unit which needs to keep the pointer of various forms of the application, to do operations on them later.
In order to do those operations, I need to cast the pointer to a form type, ex.
var
ptrFrmMain: Pointer;
CurrentFrmMain: TfrmMain;
begin
CurrentFrmMain := ptrFrmMain;
CurrentFrmMain.Close();
end;
The problem is that this unit is contained in the uses of all the other Delphi units of the application. So while I can declare a simple Pointer type in the interface section, I cannot declare a type declared in the other units (such as TfrmMain of the unit frmMain.pas).
I could solve this by placing a use in the implementation section, such as:
interface
type TMyThread = class(TThread)
Public
ptrFrmMain:Pointer
...
implementation
uses frmMain
var
CurrentFrmMain: TfrmMain;
but there is still a problem: I need the variable to be specific to my class instance, for multithread purposes, and not a generic global variable.
But I cannot place it inside my TmyThread class, since TfrmMain is not declared there and I cannot place it in the uses of the interface section.
A solution would be to place CurrentFrmMain as a local variable in all the procedures which use it and then do the CurrentFrmMain := ptrFrmMain conversion each time, but do you know a better solution?
Thank you very much in advance.
I wouldn't put a Form pointer in the thread at all. I would have the thread hold callback functions instead, or even an interface:
type
TCloseProc: procedure of object;
TMyThread = class(TThread)
public
CloseProc: TCloseProc;
...
end;
...
begin
if Assigned(CloseProc) then CloseProc();
end;
type
IMyIntf = interface(IInterface)
['{9CC7DB9E-D47F-4B7D-BBF9-6E9B80823086}']
procedure DoClose;
end;
TMyThread = class(TThread)
public
Intf: IMyIntf;
...
end;
...
begin
if Assigned(Intf) then Intf.DoClose();
end;
...
type
TfrmMain = class(TForm, IMyIntf)
public
procedure doClose;
end;
procedure TfrmMain.doClose;
begin
Close;
end;
When the thread is created, assign the Form methods to those callbacks, or pass the Form's interface implementation to the thread:
Thread := TMyThread.Create(True);
Thread.CloseProc := frmMain.Close;
Thread.Resume;
Thread := TMyThread.Create(True);
Thread.Intf := frmMain as IMyIntf;
Thread.Resume;
Either way, the thread doesn't need to know about the actual Forms at all while still catering to Form-specific functionality.
Depends upon what do you mean by "keep the pointer of various forms of the application, to do operations on them later." - what kind (or kinds) of work that is? This is a question about generic software design, about decomposition, not just circular reference or any other language-specific issue.
If all you want to do is making same work over any form - then you should derive your forms from the same BASE-FORM-CLASS and keep references to that base class, not to the specific form classes. For example if you just need to .Release them you can just keep them all as TForm type reference which they all are derived from. This is just a typical case of extracting common abstract interface.
TMyFormWithActions = class ( TForm ) .... end;
TMyForm1234 = class ( TMyFormWithActions ) .... end;
TMyFormABCD = class ( TMyFormWithActions ) .... end;
You can also extract the common functionality not into intermediate class, but into the MS COM interface like Remy shown in his answer. This however is bordering with quite different memory model (ARC one) MS COM was based upon. While I do not expect TForm have auto-destroy reference counting, I also am not totally sure it can't happen, especially in inherited and complex application. So while I do like that approach, I omitted it because sometimes in practice it might cause unexpected and premature death of objects. If you can ensure that would not happen though it might be the most clean solution.
And if you need to do DIFFERENT actions, then you can indeed not merely store references to forms themselves, but also to actions, to software snippets. Then your thread-declaring class would build a general framework to keep forms-and-procedures data cells. And then you would have extra units implementing those specific actions to be passed.
( thread-and-action interface unit ) == uses ==> ( actions for TMyFormABCD unit ) <== uses == ( TMyFormABCD form declaration unit )
As a simplified option, you can declare those actions in the same units as forms themselves. Then you would have all form-units depend upon thread-unit, but thread-unit (remade to be generic and specific forms-agnostic) would no more depend upon any of forms-unit. Probably it might be called "Inversion of control".
See this series: http://www.uweraabe.de/Blog/2010/08/16/the-visitor-pattern-part-1/
And one more scheme to design this, which can be seen as implementing BOTH of those approaches - would be using Windows Messages.
Your "common interface", your "actions" would be represented by custom WM_xxx messages (integer consts) you would make. Then your thread would use PostMessage API to signal those actions to the forms. And those forms - by implementing methods to deal with those messages ( or by non-implementing = ignoring those messages ) would provide those action-implementations.
See: http://www.cryer.co.uk/brian/delphi/howto_send_custom_window_message.htm
PostMessage can be used from external thread but can not (easily) return values. SendMessage can only be used from the main Delphi thread. Also you have to check if MyTargetForm.HandleAllocated() before posting messages.
Is it possible to put some classes into a DLL?
I have several custom classes in a project I am working on and would like to have them put in a DLL and then accessed in the main application when needed, plus if they are in a DLL I can reuse these classes in other projects if I need to.
I found this link: http://www.delphipages.com/forum/showthread.php?t=84394 which discusses accessing classes in a DLL and it mentions delegating to a class-type property but I could not find any further information on this in the Delphi help or online.
Is there any reason I should not put classes in a DLL, and if it is ok is there a better way of doing it then in the example from the link above?
Thanks
It is not possible to get a Class/Instance from a DLL.
Instead of the class you can hand over an interface to the class.
Below you find a simple example
// The Interface-Deklaration for Main and DLL
unit StringFunctions_IntfU;
interface
type
IStringFunctions = interface
['{240B567B-E619-48E4-8CDA-F6A722F44A71}']
function CopyStr( const AStr : WideString; Index, Count : Integer ) : WideString;
end;
implementation
end.
The simple DLL
library StringFunctions;
uses
StringFunctions_IntfU; // use Interface-Deklaration
{$R *.res}
type
TStringFunctions = class( TInterfacedObject, IStringFunctions )
protected
function CopyStr( const AStr : WideString; Index : Integer; Count : Integer ) : WideString;
end;
{ TStringFunctions }
function TStringFunctions.CopyStr( const AStr : WideString; Index, Count : Integer ) : WideString;
begin
Result := Copy( AStr, Index, Count );
end;
function GetStringFunctions : IStringFunctions; stdcall; export;
begin
Result := TStringFunctions.Create;
end;
exports
GetStringFunctions;
begin
end.
And now the simple Main Program
uses
StringFunctions_IntfU; // use Interface-Deklaration
// Static link to external function
function GetStringFunctions : IStringFunctions; stdcall; external 'StringFunctions.dll' name 'GetStringFunctions';
procedure TMainView.Button1Click( Sender : TObject );
begin
Label1.Caption := GetStringFunctions.CopyStr( Edit1.Text, 1, 5 );
end;
Use runtime packages for this purpose; it's exactly what they're designed for in the first place. They get loaded automatically (or can be loaded manually), and automatically set up the sharing of the same memory manager so you can freely use classes and types between them.
You're much better off using packages (which is exactly what the IDE does for much of its functionality for that very reason).
Delphi does not support either importing or exporting classes from DLLs. To import a class from another module, you need to use packages.
While the official answer is "you can't", anything is possible of course. Frameworks like Remobjects SDK and Remobjects Hydra has been doing this for a long time. The problem is that it requires you to create an infrastructure around such a system, which is not something Delphi deals with out of the box.
The first step is memory management. A DLL is injected into the process loading it, but it does not share memory management. It has to be this way since a DLL can be created in a myriad of languages, each with their own internal mechanisms. This poses a problem with safety (i.e program writing into DLL memory and visa versa).
Secondly, interface (read: content description). How will your application know what classes it can create, class members, parameter types and so on. This is why COM requires type-libraries, which describe the content of a DLL.
Third, life-time management. If memory management for the objects created from a DLL is handled by the DLL, the DLL must also release said objects.
The above steps already exists and it's called COM. You are of course free to create as many COM DLL files as you please, just remember that these have to be registered with Windows before you use them. Either "on the fly" by your application (if you have the security rights to do so) or by your installer.
This is why COM, while representing the ultimate plugin system, is rarely used by Delphi programmers, because the technical cost of using it as a plugin system outweighs the benefits.
The alternative way
If you can assure that your DLL's are only to be used by Delphi programs, then you have a second way to explore. You have to create methods to share the memory manager of your main program with the DLL (Remobjects does this). This allows you to share objects, strings and more between the DLL and the main application.
You can then use RTTI to "map" classes stored in the DLL (the DLL must do this and generate both class and method tables) which can be invoked through a proxy class you device yourself.
All in all, unless you have plenty of free time to waste, I would either buy a system like Remobjects Hydra - or stick with packages. But can it be done another way? Of course it can. But at the cost of time and hard work.
I've given up on the Delphi 7 debugger and am pretty much relying on outputdebugstrings. Is there a standard function I can call to get the contents of an object as a string like the debugger would if I set a breakpoint?
Not exactly what your looking for, but you can use RTTI to get access to the values of various published properties. The magical routines are in the TypInfo unit. The ones you are probably most interested in are GetPropList which will return a list of the objects properties, and GetPropValue which will allow you to get the values of the properties.
procedure TForm1.DumpObject( YourObjectInstance : tObject );
var
PropList: PPropList;
PropCnt: integer;
iX: integer;
vValue: Variant;
sValue: String;
begin
PropCnt := GetPropList(YourObjectInstance,PropList);
for iX := 0 to PropCnt-1 do
begin
vValue := GetPropValue(YourObjectInstance,PropList[ix].Name,True);
sValue := VarToStr( vValue );
Memo1.Lines.Add(PropList[ix].Name+' = '+sValue );
end;
end;
for example, run this with DumpObject(Self) on the button click of the main form and it will dump all of the properties of the current form into the memo. This is only published properties, and requires that the main class either descends from TPersistent, OR was compiled with {$M+} turned on before the object.
Rumor has it that a "reflector" like ability will be available in a future version of Delphi (possibly 2010).
Consider something like Codesite which is a much more complete tracing solution. It allows you to output much more complex info, and then search, print, and analyse the data. But for your purposes, you can simply send an object to it with Codesite.Send('Before', self); and you get all the RTTI available properties in the log. Do an "After" one too, and then you can compare the two in the Codesite output just by selecting both. It's saved me many times.
if delphi 7 is the .NET version, then you could do (some of) that with reflection. (not easy, but not terribly hard). if it's the normal, compiled thing, then it's a hard problem and the debugger is you best bet, apart from specialized printing functions/methods.