I have masterdata band in my fastreport. I can write code on "masterdata After print" in pascal script, but i want to know is there a way to write this code in main delphi form.
Pascal script:
procedure MasterDataOnAfterPrint(Sender : TfrxComponent)
begin
Sup_Page.Text := 'Cont on Page ' + IntToStr(<Page> + 1);
end;
You have different options to interfer your report while printing.
You might use the events AfterPrint and/or BeforePrint which will provide the component as parameter for every time it will be printed.
If you want to access another component then the one which is provided in the events, you can use FindComponent delivering the component for the page actually printed.
To access functions within the report you can call Calc with the functions name as parameter .
An other option depending on your demands is to use the GetValue event which, will be called every time a variable is evaluated, providing the name of the variable and a var parameter for the value, which will enable you to return the value you like.
A short example might be useful:
procedure TFormOrDM.frxReport1AfterPrint(Sender: TfrxReportComponent);
begin
// if Sender is TfrxMasterdata then // Filter out all Masterdatasets
if Sender.Name = 'Masterdata1' then // Filter out a specific Masterdatasets
begin
TFrxMemoView(frxReport1.FindComponent('Sup_Page')).Text := 'Cont on Page ' + FloatToStr(frxReport1.Calc('<Page>') + 1);
end;
end;
procedure TFormOrDM.frxReport1BeforePrint(Sender: TfrxReportComponent);
begin
// Another place you might use to acsess components
end;
procedure TFormOrDM.frxReport1GetValue(const VarName: string; var Value: Variant);
begin
if VarName = 'myValue' then // own variable defined in the report
Value := 'Cont on Page ' + FloatToStr(frxReport1.Calc('<Page>') + 1);
end;
Related
This is my code for searching data using the TEdit component that triggers TFDQuery with parameter:
qryItems.ParamByName('searches').AsString := Format('%%%s%%',[edtSearch.Text]);
If I remove the wildcards (Format('%%%s%%')) format, it works. The wildcards will help me filter the query.
I like the code, its clean, simple, and straight forward. But, I am still not sure if it is correct — it is not returning anything!
My question is:
Does the code above works for query filtering from TEdit.OnChangeTracking event? Otherwise, what is the correct way of doing this?
UPDATE 1:
Heres the code from TFDQuery Editor:
SELECT category.name AS category, item.name, item.description
FROM item
JOIN category ON item.category_id = category.list_id
WHERE item.description LIKE :searches
ORDER BY item.sellable
LIMIT 100
Now, I am trying to access this from this code during runtime but it is not working:
qryItems.ParamByName('searches').AsString := Format('%%%s%%',[edtSearch.Text]);
I think the culprit here is this code Format('%%%s%%',[edtSearch.Text]), I am not getting this right.
A short answer is that you want to end up with a parameter assignment like this:
FDQuery1.Params[0].AsString := '%a%';
FDQuery1.Open();
assuming the value you want to match in your LIKE expression is simply the letter a. Or, if you want to use Format, you could do something like this:
FDQuery1.Params[0].AsString := Format('%%%s%%', [edFilter.Text]);
The reason for the three hash-signs in a row is that the first one 'escapes' the second one in the expression Format evaluates, and the third one, immediately before the 's' combines with it to act as the placeholder for a string as Format constructs its result.
However, given that you are not completely familiar with working with datasets and filtering,
I think you are making this unnecessarily difficult for yourself in at least two respects:
FMX + LiveBindings is not entirely bug free and has some quirks which may well get in your way.
The syntax for using the LIKE operator, which uses hash-signs (#), clashes with the use
of hash signs for resolving parameters in the Format function. This, in particular, can be
extremely confusing, especially when you are trying to obtain a syntactically valid
LIKE expression, whether it is for inclusion in the Sql your query uses or in a 'local filter,
i.e. one which uses the Filter + Filtered properties of the FDQuery.
So, I am going to make a suggestion which might possibly be unwelcome initially,
which is to do your exploration
of things like filtering in a VCL application such as the one below. It will only take a few minutes to set up,
but will probably save you some time and wear and tear on the nervous system compared with
trying to get it right in an FMX + LiveBinding application which is under development. Here is how:
Create a new VCL application and add these components to it.
FDConnection1: TFDConnection;
FDGUIxWaitCursor1: TFDGUIxWaitCursor;
FDQuery1: TFDQuery;
DBGrid1: TDBGrid;
DBNavigator1: TDBNavigator;
DataSource1: TDataSource;
edFilter: TEdit;
btnLocalFilter: TButton;
btnSqlFilter: TButton;
Add the code below to the form's file.
Put a debugger breakpoint on the line
case FilterMode of
and exlore the app's behaviour changing the contents of the edFilter control
and clicking the two buttons, once you've adapted the code to the data you have
available. Mine uses an Author's table, I can't remember where I got it from
but maybe it was from the Pubs sample database for Sql-Server.
The app shows - as I'm sure you've gathered - that you can filter the data displayed
by your app either server-side by changing the Sql used to retrieve the data or client-side by using
the Filter property of the FDQuery. So that you can easily see what's going on, the Sql for server-side
filtering is constructed by concatenating the contents of edFilter.Text with the
rest of the Sql, but in real life, you should never do that because of
its exposure to the Sql Injection exploit.
Code
type
TFilterMode = (fmLocal, fmSql);
type
TForm1 = class(TForm)
[...]
public
{ Public declarations }
FilterMode : TFilterMode;
end;
[...]
const
sOrderBy = ' order by lastname, forename';
sSql = 'select * from authors';
sFilteredSql = sSql + ' where lastname like :lastname%';
sLocalFilter = 'lastname like ''%%s%%''';
procedure TForm1.OpenFDQuery;
var
S : String;
begin
if FDQuery1.Active then FDQuery1.Close;
FDQuery1.Params.Clear;
FDQuery1.Filter := '';
FDQuery1.Filtered := True;
case FilterMode of
fmSql : begin
FDQuery1.Sql.Text := '';
// WARNING - don't do this for real - risk of Sql Injection exploit
// use a parameterised query instead - see http://docwiki.embarcadero.com/RADStudio/Rio/en/Using_Parameters_in_Queries
S := 'select * from authors where lastname like ''%' + edFilter.Text + '%''';
FDQuery1.Sql.Text := S;
end;
fmLocal : begin
FDQuery1.Sql.Text := sSql + sOrderBy;
S := 'lastname like ''%' + edFilter.Text + '%''';
FDQuery1.Filter := S;
FDQuery1.Filtered := True;
end;
end;
FDQuery1.Open;
end;
procedure TForm1.ApplySqlFilter;
begin
FilterMode := fmLocal;
OpenFDQuery;
end;
procedure TForm1.ApplyLocalFilter;
begin
FilterMode := fmLocal;
OpenFDQuery;
end;
procedure TForm1.btnLocalFilterClick(Sender: TObject);
begin
ApplyLocalFilter;
end;
procedure TForm1.btnSqlFilterClick(Sender: TObject);
begin
ApplySqlFilter;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
edFilter.Text := 'a';
end;
I'm using Delphi's GetPropValue() function to get values of certain properties of some objects of type TControl. Everything works correctly when I get simple property values such as Value, Opacity, etc, but as I'm using firemonkey there are some extended properties, such as RotationCenter, it has RotationCenter.X and RotationCenter.Y, or even properties of text within TextSettings, in these properties with sub-types I can not get the values.
In this example I get the values correctly:
If IsPublishedProp (Component_cc, 'Value') then
EditValue.Text: = GetPropValue (Component_cc, 'Value', true);
Where Component_cc:TControl; And is created dynamically, it can also be any type of Firemonkey component (so far everything is okay, everything works).
When I need to use the form below, it does not work.
If IsPublishedProp (Component_cc, 'RotationCenter.X') then
EditRotationCenterX.Text: = GetPropValue (CC_component, 'RotationCenter.X', true);
Does anyone know a way to get these properties extended by this function?
First, the CC_component's RotationCenter property is actually an instance of TPosition class which decends from TPersistent.
Second, you cannot use dotted notation when calling IsPublishedProp.
You can use GetObjectProp to first retrieve the internal TPosition instance and then access the X property from there:
(Assume a simple FMX application with one form that contains a TButton called Button1 and a TEdit called EditRotationCenterX.)
procedure TForm1.Button1Click(Sender: TObject);
var
CC_component : TComponent;
CC_component_RotationCenter : TPosition;
begin
CC_component := Button1;
if IsPublishedProp(CC_component, 'RotationCenter') then
begin
CC_component_RotationCenter := TPosition(GetObjectProp(CC_component, 'RotationCenter'));
EditRotationCenterX.Text := CC_component_RotationCenter.X.ToString;
end
end;
Update, for a property of type Set:
For a Set type property, you will need to retrieve its ordinal value using GetOrdProp. This will be the array of bits that represent which elements are included in the current value. Then, you simply test if the appropriate bit is set. This is the method I would prefer.
Alternatively you can use GetSetProp which will return a text representation of the elements in the Set's current value. For example, if the Set's value is [TCorner.BottonLeft, TCorner.TopRight] the you will get back the string value "TopRight,BottonLeft". You then check to see if the name of your target element appears anywhere in the returned string. This method is susceptible to failure if the Delphi RTL or FMX libraries are ever changed in the future.
(This example adds a TRectangle shape called Rectangle1 and a TCheckBox called cbCornerBottonRight to the simple FMX App from above:)
procedure TForm1.Button1Click(Sender: TObject);
var
CC_component : TComponent;
CC_component_Corners : nativeint;
CC_component_CornersAsString : string;
begin
CC_component := Rectangle1;
if IsPublishedProp(CC_component, 'Corners') then
begin
// Using this method will make your code less sensitive to
// changes in the ordinal values of the Set's members or
// changes to names of the enumeration elements.
//
CC_component_Corners := GetOrdProp(CC_component,'Corners');
cbCornerBottonRight.IsChecked := ((1 shl ord(TCorner.BottomRight)) and CC_component_Corners) <> 0;
// This approach may break if the names of the elements of
// the TCorner enumeration are ever changed. (BTW, they have
// been in the past: "cvTopLeft", "cvTopRight", "cvBottomLeft",
// and "cvBottomRight" are now deprecated)
//
CC_component_CornersAsString := GetSetProp(CC_component,'Corners');
cbCornerBottonRight.IsChecked := CC_component_CornersAsString.IndexOf('BottomRight') >= 0;
end;
end;
When speaking about the old RTTI, you can do this. You need to go deeper in the structure. Ask for the X property the TPosition object:
var
O: TObject;
X: Integer;
begin
if PropIsType(Component_cc, 'RotationCenter', tkClass) then
begin
O := GetObjectProp(Component_cc, 'RotationCenter');
if Assigned(O) and PropIsType(O, 'X', tkInteger) then
X := GetOrdProp(O, 'X');
end;
end;
I'm using FastReport 4.7.31 in Turbo Delphi Pro.
The following procedure processes the data stored in several dated files depending on user input.
procedure TfrmMain.MyReportPrint;
var MDate : Tdate;
S, myfile : string;
firstone: boolean;
// Date1, Date2 & ShowPreview are global variables set via a dialog box
begin
firstone := true;
MDate := Date1;
while MDate < IncDay(Date2, 1) do
begin
DateTimeToString(S,'yyyymmdd',MDate);
myfile := 'm' + S + '.dbf';
if FileExists(DataPath + '\' + myfile) then
begin
tblPS.Close;
tblPS.TableName := myfile;
frxMyReport.PrepareReport(firstone);
firstone := false;
end;
MDate := IncDay(MDate, 1);
end;
if ShowPreview then frxMyReport.ShowReport else frxMyReport.Print;
end;
frxMyReport.Print prints all the pages.
frxMyReport.ShowReport shows only the last page prepared.
The ShowReport method takes an optional parameter ClearLastReport, and its default value is true. Whether it's true or false, ShowReport prepares the report before displaying it, so in your code, you're discarding everything you've already prepared and then re-preparing the report using the most recently assigned table settings. If the only change you were to make to your code would be to pass False to ShowReport, then you'd find that the preview showed all your pages, but repeated the last page.
In contrast to ShowReport, the Print method does not prepare the report. It only prints what has already been prepared. You want ShowPreparedReport for your preview, not ShowReport. See section 1.9 of the FastReport Programmer's Manual.
I am implementing a Boilerplate feature - allow users to Change descriptions of some components - like TLabels - at run time.
e.g.
TFooClass = Class ( TBaseClass)
Label : Tlabel;
...
End;
Var FooClass : TFooClass;
...
At design time, the value Label's caption property is say - 'First Name', when the
application is run, there is a feature that allows the user to change the caption
value to say 'Other Name'. Once this is changed, the caption for the label for
the class instance of FooClass is updated immediately.
The problem now is if the user for whatever reason wants to revert back to the design
time value of say 'First Name' , it seems impossible.
I can use the RTTIContext methods and all that but I at the end of the day, it seems
to require the instance of the class for me to change the value and since this
has already being changed - I seem to to have hit a brick wall getting around it.
My question is this - is there a way using the old RTTI methods or the new RTTIContext
stuff to the property of a class' member without instantiating the class - i.e. getting
the property from the ClassType definition.
This is code snippet of my attempt at doing that :
c : TRttiContext;
z : TRttiInstanceType;
w : TRttiProperty;
Aform : Tform;
....
Begin
.....
Aform := Tform(FooClass);
for vCount := 0 to AForm.ComponentCount-1 do begin
vDummyComponent := AForm.Components[vCount];
if IsPublishedProp(vDummyComponent,'Caption') then begin
c := TRttiContext.Create;
try
z := (c.GetType(vDummyComponent.ClassInfo) as TRttiInstanceType);
w := z.GetProperty('Caption');
if w <> nil then
Values[vOffset, 1] := w.GetValue(vDummyComponent.ClassType).AsString
.....
.....
....
....
I am getting all sorts of errors and any help will be greatly appreciated.
The RTTI System does not provide what you are after. Type information is currently only determined at compile time. Initial form values are set at Runtime using the DFM resource. You can change values in a DFM Resource in a compiled application because it's evaluated at runtime.
Parse and use the DFM Resource where it is stored, or make a copy of the original value at runtime. Possibly at the point of initial change to reduce your memory footprint.
Masons Suggestion of using TDictionary<string, TValue> is what I would use. I would be careful of storing this information in a database as keeping it in sync could become a real maintenance nightmare.
Sounds like what you're trying to do is get the value of a certain property as defined in the DFM. This can't be done using RTTI, since RTTI is based on inspecting the structure of an object as specified by its class definition. The DFM isn't part of the class definition; it's a property list that gets applied to the objects after they've been created from the class definitions.
If you want to get the values of the properties of a form's controls, you'll probably have to cache them somewhere. Try putting something in the form's OnCreate that runs through all the controls and uses RTTI to populate a TDictionary<string, TValue> with the values of all the properties. Then you can look them up later on when you need them.
If what you are trying to achieve it to restore the value that was set at design-time (i.e. that one that is saved in the DFM), I'd use InitInheritedComponent as a starting point.
It's possible to get the content of the DFM at runtime. Could be a pain to parse though.
Check also InternalReadComponentRes.
Both routine can be found in the classes unit.
Well - I solved the problem. The trick is basically instantiating another instance of the form like so :
procedure ShowBoilerPlate(AForm : TForm; ASaveAllowed : Boolean);
var
vCount : Integer;
vDesignTimeForm : TForm;
vDesignTimeComp : TComponent;
vDesignTimeValue : String;
vCurrentValue : String;
begin
....
....
vDesignTimeForm := TFormClass(FindClass(AForm.ClassName)).Create(AForm.Owner);
try
// Now I have two instances of the form - I also need to have at least one
// overloaded constructor defined for the base class of the forms that will allow for
// boilerplating. If you call the default Constructor - no boilerplating
// is done. If you call the overloaded constructor, then, boilerplating is done.
// Bottom line, I can have two instances AForm - with boilerplated values and
// vDesignForm without boilerplated values.
for vCount := 0 to AForm.ComponentCount-1 do begin
vDummyComponent := AForm.Components[vCount];
if Supports (vDummyComponent,IdoGUIMetaData,iGetGUICaption) then begin
RecordCount := RecordCount + 1;
Values[vOffset, 0] := vDummyComponent.Name;
if IsPublishedProp(vDummyComponent,'Caption') then begin
vDesignTimeComp := vDesignTimeForm.FindComponent(vDummyComponent.Name);
if vDesignTimeComp <> nil then begin
// get Design time values here
vDesignTimeValue := GetPropValue(vDesignTimeComp,'Caption');
end;
// get current boilerplated value here
vCurrentValue := GetPropValue(vDummyComponent,'Caption');
end;
vOffset := RecordCount;;
end;
end;
finally
FreeAndNil(vDesignTimeForm);
end;
end;
Anyway - thank you all for all your advice.
I wrote Delphi debug visualizer for TDataSet to display values of current row, source + screenshot: http://delphi.netcode.cz/text/tdataset-debug-visualizer.aspx . Working good, but very slow. I did some optimalization (how to get fieldnames) but still for only 20 fields takes 10 seconds to show - very bad.
Main problem seems to be slow IOTAThread90.Evaluate used by main code shown below, this procedure cost most of time, line with ** about 80% time. FExpression is name of TDataset in code.
procedure TDataSetViewerFrame.mFillData;
var
iCount: Integer;
I: Integer;
// sw: TStopwatch;
s: string;
begin
// sw := TStopwatch.StartNew;
iCount := StrToIntDef(Evaluate(FExpression+'.Fields.Count'), 0);
for I := 0 to iCount - 1 do
begin
s:= s + Format('%s.Fields[%d].FieldName+'',''+', [FExpression, I]);
// FFields.Add(Evaluate(Format('%s.Fields[%d].FieldName', [FExpression, I])));
FValues.Add(Evaluate(Format('%s.Fields[%d].Value', [FExpression, I]))); //**
end;
if s<> '' then
Delete(s, length(s)-4, 5);
s := Evaluate(s);
s:= Copy(s, 2, Length(s) -2);
FFields.CommaText := s;
{ sw.Stop;
s := sw.Elapsed;
Application.MessageBox(Pchar(s), '');}
end;
Now I have no idea how to improve performance.
That Evaluate needs to do a surprising amount of work. The compiler needs to compile it, resolving symbols to memory addresses, while evaluating properties may cause functions to be called, which needs the debugger to copy the arguments across into the debugee, set up a stack frame, invoke the function to be called, collect the results - and this involves pausing and resuming the debugee.
I can only suggest trying to pack more work into the Evaluate call. I'm not 100% sure how the interaction between the debugger and the evaluator (which is part of the compiler) works for these visualizers, but batching up as much work as possible may help. Try building up a more complicated expression before calling Evaluate after the loop. You may need to use some escaping or delimiting convention to unpack the results. For example, imagine what an expression that built the list of field values and returned them as a comma separated string would look like - but you would need to escape commas in the values themselves.
Because Delphi is a different process than your debugged exe, you cannot direct use the memory pointers of your exe, so you need to use ".Evaluate" for everything.
You can use 2 different approaches:
Add special debug dump function into executable, which does all value retrieving in one call
Inject special dll into exe with does the same as 1 (more hacking etc)
I got option 1 working, 2 should also be possible but a little bit more complicated and "ugly" because of hacking tactics...
With code below (just add to dpr) you can use:
Result := 'Dump=' + Evaluate('TObjectDumper.SpecialDump(' + FExpression + ')');
Demo code of option 1, change it for your TDataset (maybe make CSV string of all values?):
unit Unit1;
interface
type
TObjectDumper = class
public
class function SpecialDump(aObj: TObject): string;
end;
implementation
class function TObjectDumper.SpecialDump(aObj: TObject): string;
begin
Result := '';
if aObj <> nil then
Result := 'Special dump: ' + aObj.Classname;
end;
initialization
//dummy call, just to ensure it is linked c.q. used by compiler
TObjectDumper.SpecialDump(nil);
end.
Edit: in case someone is interested: I got option 2 working too (bpl injection)
I have not had a chance to play with the debug visualizers yet, so I do not know if this work, but have you tried using Evaluate() to convert FExpression into its actual memory address? If you can do that, then type-cast that memory address to a TDataSet pointer and use its properties normally without going through additional Evaluate() calls. For example:
procedure TDataSetViewerFrame.mFillData;
var
DS: TDataSet;
I: Integer;
// sw: TStopwatch;
begin
// sw := TStopwatch.StartNew;
DS := TDataSet(StrToInt(Evaluate(FExpression)); // this line may need tweaking
for I := 0 to DS.Fields.Count - 1 do
begin
with DS.Fields[I] do begin
FFields.Add(FieldName);
FValues.Add(VarToStr(Value));
end;
end;
{
sw.Stop;
s := sw.Elapsed;
Application.MessageBox(Pchar(s), '');
}
end;