Delphi-FastReport VCL 5 Conditional highlighting - delphi

I've read this manual , and follow it , but the highlighting not working.
What I have?
Create new VCL application.
Drop a TEdit and TButton components in the form (To pass the value).
Drop a TfrxReport on the form.
Open the report in design mode.
Drop a ReportTile band in the report page.
Drop a TfrxMemoView in the ReportTitle band.
Add conditions:
1- Value <= 0 -> Red color
2- Value > 0 -> Green color
The fill color of the memo still Black even the value is >0 or <=0.
The question:
Why this conditions not working? and how can I make the conditions work?
Update:
The value was passed to the TfrxMemoView component as :
procedure TForm1.Button1Click(Sender: TObject);
Var Mem : TfrxMemoView;
begin
Mem := frxReport1.FindObject('Memo1') as TfrxMemoView;
Mem.Text := Edit1.Text;
frxReport1.ShowReport();
end;

None of the rules is applied because the Value property remains NULL. To assign a constant value from Delphi code you can either write a constant expression, for example:
procedure TForm1.Button1Click(Sender: TObject);
var
Memo: TfrxMemoView;
begin
Memo := frxReport1.FindObject('Memo1') as TfrxMemoView;
Memo.Text := Format('[%s]', [Edit1.Text]);
frxReport1.ShowReport;
end;
In the above code I've omitted check if the control was found. And, you need to be careful with the input text. It accepts only floating point values in format that won't collide in decimal separator with separator(s) defined in the ExpressionDelimiters property.
Or simply set the Value property as well:
procedure TForm1.Button1Click(Sender: TObject);
var
Memo: TfrxMemoView;
begin
Memo := frxReport1.FindObject('Memo1') as TfrxMemoView;
Memo.Text := Edit1.Text;
Memo.Value := StrToFloat(Edit1.Text);
frxReport1.ShowReport;
end;
In this one the check if the control was found is missing as well. And the conversion to float is not necessary there. The Value can be just a string convertible to float.

Related

How to get Id values for the adjacent records in TcxGrid (TcxGridDBTableView)

I have DevExpress cxGrid and I would like to get the Ids of the adjacent records. I can use them in the following use case: user deletes focused record and I position the (grid view) cursor on one of the adjacent records (as determined but the current sorting and grouping order of the grid view). Otherwise the position of the grid cursor is poorly determined after removal of the record and after refreshing the grid view.
I have made the following attempts but they are not working - the Id values are junk:
procedure GetAdjacentRecordIds(AView: TcxGridDBTableView; ACdFieldName: string; var APrevId, AId, ANextId: Integer);
var Item: TcxCustomGridTableItem;
RecIdx: Integer;
i, RecCount: Integer;
begin
APrevId:=-1;
AId:=-1;
ANextId:=-1;
if Trim(AIdFieldName)='' then Exit;
if not Assigned(AView) then Exit;
if not Assigned(AView.DataController) then Exit;
Item:=AView.DataController.GetItemByFieldName(UpperCase(AIdFieldName));
if not Assigned(Item) then Exit;
{//First attempt, didn't work, AId was the right one, but APrevId and ANextId were junk
RecIdx:=AView.DataController.FocusedRecordIndex;
AId:=AView.DataController.Values[RecIdx, Item.Index];
APrevId:=AView.DataController.Values[RecIdx-1, Item.Index];
ANextId:=AView.DataController.Values[RecIdx+1, Item.Index];}
//Second attempt, doesn't work, all three Ids are junk
RecIdx:=-1;
RecCount:=AView.ViewData.RecordCount;
for i:=0 to RecCount-1 do begin
if AView.ViewData.Records[i].Focused then begin
RecIdx:=1;
Break;
end;
end;
if RecIdx<0 then Exit;
AId:=AView.ViewData.Records[RecIdx].Values[Item.Index];
if RecIdx>0 then
APrevId:=AView.ViewData.Records[RecIdx-1].Values[Item.Index];
if RecIdx<RecCount then
ANextId:=AView.ViewData.Records[RecIdx+1].Values[Item.Index];
end;
How can I correct this code to get the field values for adjacent records. Or maybe I should use Grid navigator and do prev/next on it, but I would like to find the values in invisible. And DataSet.DisableControls may stop the Grid navigator?
I used the DevExpress knowledgebase to find this answer. You are referencing the internal storage of the grid incorrectly. You can also use dataset.previous and dataset.next to position the record pointer in a bound grid, and simply use dataset.fieldbyname(AidFieldName).AsInteger to retrieve key values.
get-cell-value-by-column-name
procedure TForm1.Button1Click(Sender: TObject);
var
AColumn: TcxGridDBColumn;
I: Integer;
AView: TcxGridDBTableView;
v: Variant;
begin
AView := cxGrid1DBTableView1;
AColumn := TcxGridDBColumn( AView.FindItemByName('cxGrid1DBTableView1Capital'));
if Assigned(AColumn) then
for I := 0 to AView.ViewData.RowCount - 1 do
v := AView.DataController.Values[AView.ViewData.Rows[0].RecordIndex, AColumn.Index];
end;
Use of the navigation functions of DataSet is not the solution, because CxGrid can have completely different sorting order. But DataController navigation is good solution and there is no visual flickering as well. So, the solution is:
RecIdx:=AView.DataController.FocusedRecordIndex;
AId:=AView.DataController.Values[RecIdx, Item.Index];
if not AView.DataController.IsBOF then begin
AView.DataController.GotoPrev;
RecIdx:=AView.DataController.FocusedRecordIndex;
APrevId:=AView.DataController.Values[RecIdx, Item.Index];
AView.DataController.GotoNext;
end;
if not AView.DataController.IsEOF then begin
AView.DataController.GotoNext;
RecIdx:=AView.DataController.FocusedRecordIndex;
ANextId:=AView.DataController.Values[RecIdx, Item.Index];
AView.DataController.GotoPrev;
end;

How to make percentage from calculation record divided with summary DBGRID Delphi

I have dbgrid which display column subtotal and column percentage, how to display column percentage from this formula : (subtotal / grandtotal) * 100% ? for detail information please see below picture
I couldnt modify my SQL, because my SQL is very complicated, so i think solution maybe use calculated field, doesnt it? Could someone help me to solve this problem.
Thanks in advanced.
The following assumes that your dataset doesn't actually contain the last row you've shown, the one that contains "111077, 100" - if it does, then the steps I show below to calculate the GrandTotal are unnecessary, and you only need to populate the Percent calculated field, which is trivial.
If your DataSet is a TClientDataSet, you can implement the Percent values quite easily using the
combination of a TAggregateField to represent the GrandTotal and a calculated field to represent each data row's contribution towards the GrandTotal. See code below.
If you are not using a TClientDataSet already then you have several options,
including
If your DataSet is of a type which supports aggregate fields then you can do the equivalent of the code below.
Use your existing DataSet as the dataset source of a TDataSetProvider, and use the TDataSetProvider as the Provider of a TClientDataSet and use the TClientDataSet to supply the data to your grid.
Don't use a TClientDataSet and/or TAggregateField and instead do similar to what is shown below with your existing DataSet, but make the Percent field an fkInternalCalc field if your DataSet type supports it, or an fkCalculated one if not, omit the GrantTotal TAggregateField field and calculate the GrandTotal in code. One way to do this would be to calculate it by a once-only traversal of the dataset (while not DataSet.Eof ...) after you open it.
In the code below, I've created all the fields in code, rather than using the Object Inspector's Fields editor, so you can easily see exactly what are the minimum settings necessary to get a TAggregateField to work.
Note: I could be wrong but don't think you could get a standard TDBGrid to display the final, 100%, row of your screenshot. Somthething similar could be done using the Developer Express TcxGrid, amongst others, but if you need a TDBGrid to do this, you should ask how to in a new question.
Code
TForm1 = class(TForm)
CDS: TClientDataSet;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
procedure CDSCalcFields(DataSet: TDataSet);
procedure FormCreate(Sender: TObject);
private
CDSID : TIntegerField;
CDSTotal : TCurrencyField;
CDSPercent : TFloatField;
CDSGrandTotal : TAggregateField;
public
procedure SetUp;
end;
[...]
procedure TForm1.SetUp;
var
i : Integer;
begin
CDSID := TIntegerField.Create(Self);
CDSID.FieldName := 'ID';
CDSID.FieldKind := fkData;
CDSID.DataSet := CDS;
CDSTotal := TCurrencyField.Create(Self);
CDSTotal.FieldName := 'Total';
CDSTotal.FieldKind := fkData;
CDSTotal.DataSet := CDS;
CDSPercent := TFloatField.Create(Self);
CDSPercent.FieldName := 'Percent';
CDSPercent.FieldKind := fkInternalCalc;
CDSPercent.DataSet := CDS;
CDSGrandTotal := TAggregateField.Create(Self);
CDSGrandTotal.FieldName := 'GrandTotal';
CDSGrandTotal.FieldKind := fkAggregate;
CDSGrandTotal.Expression := 'Sum(Total)';
CDSGrandTotal.DataSet := CDS;
CDSGrandTotal.Active := True;
CDS.OnCalcFields := CDSCalcFields;
CDS.IndexFieldNames := 'ID';
CDS.CreateDataSet;
for i := 1 to 2 do begin
CDS.InsertRecord([i, i]);
end;
CDS.First;
end;
procedure TForm1.CDSCalcFields(DataSet: TDataSet);
var
Value : Double;
V : Variant;
begin
V := CDSGrandTotal.Value;
if not VarIsNull(V) then begin
Value := CDSTotal.AsFloat;
Value := Value * 100 / V;
CDSPercent.Value := Value;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
SetUp;
end;

Loop through records on a cxgrid and update a field/column

I have a cxGrid where I apply a filter to select certain records. When that is done I want to be able to update a field/column in the grid to mark each record that is to be used for the next operation.
I haven't been able to figure this out
Maybe I haven't been specific enough when describing my problem.
I have the cxGrid where I have applied a filter selecting some records.
What I then need to do is to click a columnheader and then have a field called fldselected set to True for these records.
What your updated q is asking for is straightforward and as usual with Devex stuff, it's
all in the OLH as long as you can find your way into it.
A way to find which rows currently match the filter is to use the
cxGrid1DBTableView1.DataController.FilteredRecordIndex[]
property. You can then find that record in the dataset to process it in some way using
cxGrid1DBTableView1.DataController.LocateByKey().
Update: The original version of this answer assumed that the dataset had an integer ID field.
As the OP has said he uses GUIDs instead, I've upddated it accordingly.
Assuming the TClientDataSet CDS1 has fields Guid : TGuidField, Name : TStringfield, size 32
and Selected : TBooleanField and is connected to
a cxDBTableView, with filtering enabled, of a TcxGrid.
Make sure the cxGrid1DBTableView1.DataController.KeyFieldNames is set to 'Guid'.
Add a regular TDBGrid to the form and point it at the same datasource as the TcxGrid. The point
of this is to make it easy to verify that the code is working as required.
Add the code below to the unit, and point cxDBTableView1's OnColumnHeaderClick at
the handler cxGrid1DBTableView1ColumnHeaderClick, and the form's OnCreate at the FormCreate.
Compiler & run
Code:
procedure TForm1.cxGrid1DBTableView1ColumnHeaderClick(Sender: TcxGridTableView;
AColumn: TcxGridColumn);
begin
if AColumn = cxGrid1DBTableView1Name then
ProcessFilteredRecords;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
AGuid : TGuid;
i : Integer;
lResult : Longint;
begin
CDS1.IndexFieldNames := 'Name';
CDS1.CreateDataSet;
for i:= 0 to 6 do begin
lResult := SysUtils.CreateGUID(AGuid);
CDS1.Insert;
CDS1.FieldByName('Name').AsString := Chr(Ord('A') + i);
CDS1.FieldByName('Guid').AsString := GuidToString(AGuid);
CDS1.FieldByName('Selected').AsBoolean := False;
CDS1.Post;
end;
CDS1.First;
end;
procedure TForm1.ProcessFilteredRecords;
var
V : Variant;
i,
Index: Integer;
BM : TBookMark;
begin
BM := CDS1.GetBookMark;
CDS1.DisableControls;
try
for i := 0 to cxGrid1DBTableView1.DataController.FilteredRecordCount - 1 do begin
Index := cxGrid1DBTableView1.DataController.FilteredRecordIndex[i];
// Next, get the GUID value of the row
V := cxGrid1DBTableView1.DataController.Values[Index, 0];
if cxGrid1DBTableView1.DataController.LocateByKey(V) then begin
CDS1.Edit;
CDS1.FieldByName('Selected').AsBoolean := True;
CDS1.Post;
end;
end;
finally
CDS1.EnableControls;
CDS1.GotoBookmark(BM);
CDS1.FreeBookmark(BM);
end;
end;
Check out https://www.devexpress.com/Support/Center/Question/Details/A1095, the article from Dev Express. Don't let the fact that the article is 11 years old fool you. The same technique still applies. And you can set this up either in code or in the grid editor.
Create the column in the grid editor.
Set the columns DataBinding.ValueType to Boolean (if that's what you want the checkbox to represent)
Set the Data Controller's KeyFieldNames property. Very important! I have spent hours scratching my head with an non-functioning unbound column only to find that the KeyFieldNames wasn't set.
An unbound column can be referenced in your next operation using the DataController Records or Values array, depending on how you set that up. Because it is unbound you cannot reference it through the underlying DataSet though.

write specific line in tmemo according to line number

I am using the following to insert text from a text file into TMemo.
procedure TForm1.Button1Click(Sender: TObject);
var
SL: TStringList;
begin
SL := TStringList.Create;
try
SL.LoadFromFile('c:\testimeng\keyfil.txt');
Memo1.Lines.Assign(SL);
finally
SL.Free;
end;
end;
What i want to know is how to add a single line according to row number to TMemo when i choose the specific row number.
Example output:
During this time he has distinguished himself in the academic, sporting and cultural spheres of school life.
During this time he has distinguished himself in the academic and sporting spheres of school life.
During this time he has distinguished himself in the academic and cultural spheres of school life.
During this time he has distinguished himself in the academic aspect of school life.
During this time he has distinguished himself in both the sporting and cultural aspects of school life.
Any help appreciated.
I think you're asking about putting a single line from the TStringList into the TMemo when you specify which item (index, or line number) from the TStringList. If that's the case, you can use something like this:
Memo1.Lines.Add(SL[Index]);
So if the first line in your keyfile.txt is
During this time he has distinguished himself in the academic, sporting and cultural spheres of school life.
You would use
Memo1.Lines.Add(SL[0]); // Desired line number - 1
Ok, after your comment to your question, I think I know what you're wanting to do. Here's one way to do it:
Drop a TListBox, a TButton, and a TMemo on your form. I arranged mine with the ListBox on the left, the button next to it (at the top right corner), and then the memo just to the right of the button.
In the FormCreate event, populate the TListBox with your text file and clear the existing memo content:
procedure TForm1.FormCreate(Sender: TObject);
begin
Memo1.Clear;
ListBox1.Items.LoadFromFile('c:\testimeng\keyfil.txt');
end;
Double-click the button to add an OnClick handler:
procedure TForm1.Button1Click(Sender: TObject);
var
s: string;
begin
// If there's an item selected in the listbox...
if ListBox1.ItemIndex <> -1 then
begin
// Get the selected item
s := ListBox1.Items[ListBox1.ItemIndex];
// See if it's already in the memo. If it's not, add it at the end.
if Memo1.Lines.IndexOf(s) = -1 then
Memo1.Lines.Add(s);
end;
end;
Now run the app. Click on an item in the listbox, and then click the button. If the item is not already present in the memo, it will be added as a new last line. If it's already there, it won't be added (to prevent duplicates).
If you're wanting to add it to the end of the current last line (extending the paragraph, perhaps), then you'd do it like this:
// Add selected sentence to the end of the last line of the memo,
// separating it with a space from the content that's there.
Memo1.Lines[Memo1.Lines.Count - 1] := Memo1.Lines[Memo1.Lines.Count - 1] + #32 + s;
So, it should be clear by now that to add to the end of a specific line, you just grab the content that's already
there and add to it. For instance, if the user types 3 into a TEdit:
procedure TForm1.FormCreate(Sender: TObject);
begin
SL := TStringList.Create;
SL.LoadFromFile('c:\testimeng\keyfil.txt');
end;
procedure TForm1.ButtonAddTextClick(Sender: TObject);
var
TheLine: Integer;
begin
// SL is the TStringList from the FormCreate code above
TheLine := StrToIntDef(Edit1.Text, -1);
if (TheLine > -1) and (TheLine < Memo1.Lines.Count) then
if TheLine < SL.Count then
Memo1.Lines[TheLine] := Memo1.Lines[TheLine] + SL[TheLine];
end;
Write a specific line with String by Mouse Clicking on TMemo
Procedure TForm1.Button1Click(Sender: TObject);
Var SL: TStringList;
LineNumber : Integer;
Begin
LineNumber := Memo1.Perform(EM_LINEFROMCHAR, Memo1.SelStart, 0);
Memo1.SelStart := Memo1.Perform(EM_LINEINDEX, LineNumber, 0);
Memo1.SelLength := Length(Memo1.Lines[LineNumber]) ;
Memo1.SetFocus;
SL := TStringList.Create;
try
SL.LoadFromFile('c:\testimeng\keyfil.txt');
Memo1.SelText := SL.Strings[0];
finally
SL.Free;
end;
End;

Delphi label values sorting

Im trying to sort Label's values. I have lots of labels with an integer value. Labels are called like Label1, Label2, [...], which Im accessing through FindComponent. I have no problem in sorting the integer values Ive stored in an array, but the problem is, after sorting, I have no idea which label had what value. My goal is to like, sort those labels by their value, so I'd get like an array with Labels sorted by their value. Im stuck at this point :(
Eg:
Label1.Caption := 10;
Label2.Caption := 4;
Label3.Caption := 7;
for i := 1 to 3
do some_array[i] := StrToInt(TLabel(FindComponent('Label' + IntToStr(i))).Caption);
sortarray(some_array);
Now, I have sorted array, but Im lacking some sort procedure that would also store label number in the corresponding place. Can someone point me out?
Instead of creating an array of integers, create an array of TLabel controls. This one you can sort the same way as the array of integers. Indeed, given a MyLabel: TLabel, you can easily get the associated integer as StrToInt(MyLabel.Caption).
In addition, the FindComponent approach is not very efficient. I'd do
const
ALLOC_BY = 100;
MAGIC_TAG = 871226;
var
i: Integer;
ActualLength: integer;
FLabels: array of TLabel;
begin
SetLength(FLabels, ALLOC_BY);
ActualLength := 0;
for i := 0 to ControlCount - 1 do
if Controls[i] is TLabel then
with TLabel(Controls[i]) do
if Tag = MAGIC_TAG then
begin
if ActualLength = length(FLabels) then
SetLength(FLabels, length(FLabels) + ALLOC_BY);
FLabels[ActualLength] := Controls[i];
inc(ActualLength);
end;
SetLength(FLabels, ActualLength);
SortArray(FLabels) // with respect to the StrToInt(CurLabel.Caption) of each
// CurLabel: TLabel.
Of course, you can skip the chunk allocating if you know the number of labels in advance.
Make sure that each of the labels that are to be included in the array have the Tag set to MAGIC_TAG.
Another option would be to create an array
FLabelDataArray: array of TLabelData;
of
type
TLabelData = record
Control: TLabel;
Value: integer;
end;
where
FLabelDataArray[i].Value := StrToInt(FLabelDataArray[i].Control.Caption);
is computed only once.
A quick-n-dirty solution that also works in old Delphi versions, is to use TStringList, which has a Sort method and an Objects property that allow you to associate one object to each entry in the list.
Note that the list is sorted in lexicographic order, so the integers must be left padded with zeroes when converted to strings.
var
list: TStringList;
i: integer;
lab: TLabel;
begin
Label1.Caption := '10';
Label2.Caption := '4';
Label3.Caption := '7';
list := TStringList.Create;
try
for i := 1 to 3 do begin
lab := TLabel(FindComponent('Label' + IntToStr(i)));
list.AddObject(Format('%10.10d', [StrToInt(lab.Caption)]), lab);
end;
list.Sort;
for i := 0 to list.Count-1 do
Memo1.Lines.Add(list[i] + #9 + TLabel(list.Objects[i]).Name);
finally
list.Free;
end;
end;
The output would be:
0000000004 Label2
0000000007 Label3
0000000010 Label1
Also, if instead of list.Sort you use list.Sorted := true, you get binary search on list as a bonus (using list.IndexOf or list.Find).
Edit: As Rudy says, visual components such as TLabel should only be used for displaying data, and not for storing and manipulating it. It is recommended to use appropiate data structures for this and to separate the logic of the program from its user interface.
As Andreas says, put the labels into a dynamic array, rather than sorting the values. Once you have them in such an array, sort them like this:
uses
Generics.Defaults, Generics.Collections;
procedure SortLabels(var Labels: array of TLabel);
var
Comparison: TComparison<TLabel>;
Comparer: IComparer<TLabel>;
begin
Comparison := function(const Left, Right: TLabel): Integer
begin
Result := StrToInt(Left.Caption)-StrToInt(Right.Caption);
end;
Comparer := TDelegatedComparer<TLabel>.Create(Comparison);
TArray.Sort<TLabel>(Labels, Comparer);
end;
As others have said, I don't think you're taking the right approach for this task. But, based on your question, a simple hack would be to use the tag property on each label to store its caption's value :
Label1.Caption := '10';
Label1.Tag:=10;
Label2.Caption := '4';
Label2.Tag:=4;
Label3.Caption := '7';
Label3.Tag := 7;
Then you can find the appropriate label by matching the 'array of integer' entry with the label's tag property.
Again: As Rudy and others have commented, your approach to this task is far from desirable, and my solution only conforms to your approach. The tag property itself is a hack built into Delphi, an artifact from ancient times, like 'label' and 'goTo' and really should not be used except out of sheer desperation - like when trying to re-work and retro-fit ancient, poorly written code or if you've got something in prod and you must get in a quick fix for an emergency situation.

Resources