Firemonkey: TGrid usage on Embarcadero C++ Builder XE3 - c++builder

I'm try to build a tool that reads data from a database and displays it as a table using a TGrid in Firemonkey. I need to use different types of columns like TCheckColumn and TPopupColumn but can't find any good guide or example on how to use them in C++ Builder.
Any way, I managed to understand the usage of the TStringColumn,TProgressColumn setting the Value of the cell in the TGrid's event onGetValue.
Does any one of you know how to set the Value for columns of type TCheckColumn, TImageColumn and TPopupColumn?
thanks
Daniele
---UPDATE---
I managed to use the TProgressColumn. This is what I do in the Form's constructor:
// TStringColumn
Grid1->AddObject(new TStringColumn(this));
// TCheckColumn
TCheckColumn* c = new TCheckColumn(this);
Grid1->AddObject(c);
// TPopupColumn
// list of values
TStringList * l = new TStringList(NULL);
l->Add(L"First");
l->Add(L"Second");
l->Add(L"Third");
TPopupColumn* p = new TPopupColumn(this);
// adding the list to the PopupColumn
p->Items = l;
Grid1->AddObject(p);
// TProgressColumn
Grid1->AddObject(new TProgressColumn (this));
Grid1->RowCount = 3 ;
and this is the Grid1GetValue method:
// TStringColumn
if(Col == 0) Value = TValue::From<String>(Row);
// TCheckColumn !! Can't make it work
if(Col == 1) Value = TValue::From<Boolean>(true);
// TPopupColumn
if(Col == 2) Value = TValue::From<int>(2);
// TProgressColumn
if(Col == 3) Value = TValue::From<double>(50.0);
---UPDATE---
if I save the value of the column using the method OnSetValue
void __fastcall TForm1::Grid1SetValue(...)
{
if(Col == 1) check = Value;
}
and then set it with the method OnGetValue:
void __fastcall TForm1::Grid1GetValue(...)
{
// TCheckColumn !! Can't make it work
if(Col == 1) Value = check;// TValue::From<Boolean>(true);
}
After I click on one checkbox all the other checkboxes change state. So the component works correctly... now the point is how to set the Value to true or false in the right way.

TGris does not store any data, you should create your own datastorage.
Example: TGrid with TCheckColumn, TStringColumn and TPopupColumn
type
TField = record
Checked: Boolean;
Name: string;
Column: Byte;
end;
var
Fields: TList<TField>;
function SetField(const AChecked: Boolean; const AName: string; const AColumn: Byte): TField;
begin
with Result do begin
Checked := AChecked;
Name := AName;
Column := AColumn;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
begin
Fields := TList<TField>.Create;
Fields.Add(SetField(True, 'Name', 1));
Fields.Add(SetField(True, 'Login', 2));
Fields.Add(SetField(True, 'Password', 3));
for I := 1 to Fields.Count do
PopupColumn1.Items.Add('Column ' + IntToStr(I));
gdFields.RowCount := Fields.Count;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Fields.Free;
end;
procedure TFormExport.gdFieldsGetValue(Sender: TObject; const Col, Row: Integer; var Value: TValue);
begin
case gdFields.Columns[Col].TabOrder of
0: Value := Fields[Row].Checked;
1: Value := Fields[Row].Name;
2: Value := Fields[Row].Column - 1;
end;
end;
procedure TFormExport.gdFieldsSetValue(Sender: TObject; const Col, Row: Integer; const Value: TValue);
var
FRec: TField;
begin
FRec := Fields[Row];
case gdFields.Columns[Col].TabOrder of
0: FRec.Checked := Value.AsBoolean;
1: FRec.Name := Value.AsString;
2: FRec.Column := Value.AsInteger + 1;
end;
Fields[Row] := FRec;
end;
Now all data from your datastorage will be changed after editing your TGrid, but possible bug in TGrid - never received OnSetValue after changing PopupColumn

I can't give C++ code but a Delphi example should be easy enough to translate.
You get and set all cell values the same way, by listening for the OnGetData and OnSetData events, get take/give values of type TValue (XE3 and later). It's just a case of returning the appropriate type in the TValue:
uses System.RTTI;
procedure Form1.Grid1GetValue(Sender: TObject;const Col, Row: Integer;var Value: TValue);
begin
if Col = 1 then
Value := TValue.From<Integer>(1)
else if Col = 2 then
Value := TValue.From<String>('Hello')
else if Col = 3 then
Value := Tvalue.From<Single>(1.0);
end;
procedure Form1.Grid1SetValue(Sender: TObject;const Col, Row: Integer;const Value: TValue);
begin
if Col = 1 then
I := Value.As<Integer>
else if Col = 2 then
St := Value.As<String>
else if Col = 3 then
Si := Value.As<Single>;
end;
As far as I can tell a popup menu can't accept or give data.

In order to solve your problem, redefine the TCheckCell class in the following way:
#include <FMX.Grid.hpp>
#include <boost/dynamic_bitset.hpp>
class CheckCellClass:public TCheckCell
{
public:
__fastcall virtual CheckCellClass(System::Classes::TComponent*AOwner):TCheckCell(AOwner)
{
};
virtual System::Rtti::TValue __fastcall GetData(void)
{
return TValue::From<bool>(this->IsChecked);
};
virtual void __fastcall SetData(const TValue&Value)
{
TValue V(Value);
this->IsChecked=V.AsBoolean();
};
};
//Redifine TCheckColumn class
class CheckColumnClass:public TCheckColumn
{
private:
virtual Fmx::Controls::TStyledControl*__fastcall CreateCellControl(void)
{
CheckCellClass*Cell=new CheckCellClass(this);
Cell->OnChange =&(this->DoCheckChanged);
return Cell;
};
public:
__fastcall CheckColumnClass(System::Classes::TComponent*AOwner):TCheckColumn(AOwner)
{
};
};
//global Data for Save curent State Cell
boost::dynamic_bitset<unsigned char>FullDiscreteInputs;
Add To Grid In Constuctor
FullDiscreteInputs.resize(100);
DiscreteInputsGrid->RowCount=FullDiscreteInputs.size();
CheckColumnClass* DiscreteInPutsCheckColumn =new CheckColumnClass(DiscreteInputsGrid);
DiscreteInputsGrid->AddObject(CoilsCheckColumn);
void __fastcall TForm1::DiscreteInputsGridGetValue(TObject*Sender, const int Col, const int Row,TValue&Value)
{
//...
if(DiscreteInputsGrid->ColumnByIndex(Col)==DiscreteInPutsCheckColumn)
{
Value=TValue::From<bool>(FullDiscreteInputs[Row]);
}
//...
}
//---------------------------------------------------------------------------
void __fastcall TForm1::DiscreteInputsGridSetValue(TObject*Sender, const int Col, const int Row, const TValue&Value)
{
TValue V(Value);
if(DiscreteInputsGrid->ColumnByIndex(Col)==DiscreteInPutsCheckColumn)
{
FullDiscreteInputs[Row]=V.AsBoolean();
}
}
//---------------------------------------------------------------------------

Related

What Delphi type for 'set of integer'?

I have several hardcoded validations like these:
const
cLstAct = 1;
cLstOrg = 4;
cLstClockAct = 11;
const
FUNCT_1 = 224;
FUNCT_2 = 127;
FUNCT_3 = 3;
if lFuncID in [FUNCT_1,FUNCT_2,FUNCT_3] then ...
if not (lListType in [cLstAct..cLstOrg,cLstClockAct]) then ...
if not (lPurpose in [0..2]) then ...
that I want to replace with a common method like
function ValidateInSet(AIntValue: integer; AIntSet: ###): Boolean;
begin
Result := (AIntValue in AIntSet);
if not Result then ...
end;
but what type to choose for AIntSet?
Currently the values to be tested throughout the code go up to a const value 232 (so I can e.g. use a TByteSet = Set of Byte), but I can foresee that we will bump into the E1012 Constant expression violates subrange bounds when the constant values exceed 255.
My Google-fu fails me here...
(Currently on Delphi Seattle Update 1)
Use a dictionary, TDictionary<Integer, Integer>. The value is irrelevant and you only care about the key. If the dictionary contains a specific key then that key is a member of the set. Use AddOrSetValue to add a member, Remove to delete a member and ContainsKey to test membership.
The point of using a dictionary is that it gives you O(1) lookup.
You don't want to use this type directly as a set. You should wrap it in a class that just exposes set like capabilities. An example of that can be found here: https://stackoverflow.com/a/33530037/505088
You can use an array of Integer:
function ValidateInSet(AIntValue: integer; AIntSet: array of Integer): Boolean;
var
I: Integer;
begin
Result := False;
for I := Low(AIntSet) to High(AIntSet) do
begin
if AIntSet[I] = AIntValue then
begin
Result := True;
Break;
end;
end;
if not Result then ...
end;
const
cLstAct = 1;
cLstOrg = 4;
cLstClockAct = 11;
const
FUNCT_1 = 224;
FUNCT_2 = 127;
FUNCT_3 = 3;
if ValidateInSet(lFuncID, [FUNCT_1, FUNCT_2, FUNCT_3]) then ...
if not ValidateInSet(lListType, [cLstAct, 2, 3, cLstOrg, cLstClockAct]) then ...
if not ValidateInSet(lPurpose, [0, 1, 2]) then ...
If you are on a recent Delphi version, you can use TArray<Integer>.
function ValidateInSet(AIntValue: integer; const AIntSet: TArray<Integer>): Boolean;
var
N: Integer;
begin
{ option1 : if AIntSet is always sorted }
result := TArray.BinarySearch(AIntSet, AIntValue, N);
{ option 2: works for any array }
result := false;
for N in AIntSet do begin
if AIntValue = N then begin
result := true;
Break;
end;
end;
if not Result then begin
// ...
end;
end;
Calling is merely the same as with a set (except for ranges):
if ValidateInSet(lFuncID, [FUNCT_1,FUNCT_2,FUNCT_3]) then begin
end;
The direct answer would be TBits class
http://docwiki.embarcadero.com/Libraries/Seattle/en/System.Classes.TBits.Bits
Note: This can only be used starting with Delphi XE4 though - http://qc.embarcadero.com/wc/qcmain.aspx?d=108829
However for your "Set of integers" it in most inflated case would take 2^31 / 8 bytes of memory (because negative values of integer would not be even considered), and that would be a lot...
So I hope you would never really want to have a set of the whole integer. Or you should invest into Sparse Arrays instead.
function ValidateInSet(const AIntValue: integer; const AIntSet: TBits): Boolean;
begin
Result := (AIntValue >= 0) and (AIntValue < AIntSet.Size);
if Result then
Result := AIntSet.Bits[AIntValue];
if not Result then ...
v-a-l-i-d-a-t-e
end;
or rather
function ValidateInSet(const AIntValue: integer; const AIntSet: TBits): Boolean;
begin
Result := false;
if AIntValue < 0 then exit; // Validation criterion #1
if AIntValue >= AIntSet.Size then exit; // Validation criterion #2
if not AIntSet.Bits[AIntValue] then exit; // Validation criterion #3
if .... then exit; // Validation criterion #4
if .... then exit; // Validation criterion #5
if .... then exit; // Validation criterion #6
Result := true;
end;
or perhaps
TSetTestCriterion = TFunc<Integer, Boolean>;
TSetTestCriteria = TArray<TFunc<Integer, Boolean>>;
function ValidateInSet(const AIntValue: integer;
const AIntSet: TBits; const Tests: TSetTestCriteria = nil): Boolean;
var ExtraTest: TSetTestCriterion;
begin
Result := false;
if AIntValue < 0 then exit; // Validation criterion #1
if AIntValue >= AIntSet.Size then exit; // Validation criterion #2
if not AIntSet.Bits[AIntValue] then exit; // Validation criterion #3
if Tests <> nil then // Validation criteria #4, #5, #6, ...
for ExtraTest in Tests do
if not ExtraTest(AIntValue) then exit;
Result := true;
end;
http://docwiki.embarcadero.com/Libraries/Seattle/en/System.SysUtils.TFunc
Now - just for demo, in real app you would create those set and array once and cache for long (forever, or at least unless the configuration change would demand rebuilding them).
Type FuncIDs = ( FUNCT_3 = 3, FUNCT_2 = 127, FUNCT_1 = 224);
var MysticGlobalFlag: Boolean;
function ValidateFuncID( const lFuncID: FuncIDs): Boolean;
var map: TBits;
begin
map := TBits.Create;
try
map.Size := High(lFuncID) + 1;
map.Bits[ Ord(Func_1) ] := True;
map.Bits[ Ord(Func_2) ] := True;
map.Bits[ Ord(Func_3) ] := True;
Result := ValidateInSet( Ord(lFuncID), map,
TSetTestCriteria.Create(
function( lFuncID: integer) : Boolean
begin
Result := MysticGlobalFlag or (lFuncID <> Ord(FuncIDs.FUNC_2))
end
,
function( lFuncID: integer) : Boolean
begin
Result := (lFuncID <> Ord(FuncIDs.FUNC_3)) or (DayOfTheWeek(Now()) = 4)
end
)
);
finally
map.Destroy;
end;
if not Result then // from the original question code
... // seems like a placeholder for error handling or object creation and registration
end;
All, I know it's years since people answered this, but here is a new solution using Delphi generics: -
interface
uses
System.Generics.Defaults;
type
TUtilityArray<T> = class
public
class function Contains(const x : T; const an_array : array of T) : boolean;
end;
implementation
class function TUtilityArray<T>.Contains(const x: T; const an_array: array of T): boolean;
var
y : T;
l_comparer : IEqualityComparer<T>;
begin
Result := false;
l_comparer := TEqualityComparer<T>.Default;
for y in an_array do
begin
if l_comparer.Equals(x, y) then
begin
Result := true;
break;
end;
end;
end;
end.
To use include the class, then write if(TUtilityArray<integer>.Contains(some integer value, [value1, value2 etc.])) then .... An added benefit of this method is that it works for other primitives as well.

Fill local array pointing to field of derived class, but later field empty

I must be overlooking something in my web service...
Data structures:
TResIDNameRec = record
id : Integer;
name: String;
end;
TResIDNameArr = array of TResIDNameRec;
TResBase = class(TJSONStructure) // just a class(TObject)
private
public
&type : String;
success : Integer;
errormessage: String;
threadid : Integer;
constructor Create; overload; // empty
constructor Create(ADescendantClassName: String); overload; // sets &type
end;
TResClass = class of TResBase;
TResGetList = class(TResBase)
private
public
activities,
projects,
customers: TResIDNameArr;
constructor Create; overload; // inherited Create(Self.ClassName);
end;
and then a
type
TWebAct = (
ttlogin,
...
ttgetlist,
...
ttgetversion
);
const
cWebActStructures: Array[TWebAct] of
record
RequestClass : TReqClass;
ResponseClass: TResClass;
end
= (
{ ttlogin } (RequestClass: TReqLogin; ResponseClass: TResLogin;),
...
{ ttgetlist } (RequestClass: TReqGetList; ResponseClass: TResGetList;),
...
{ ttgetversion} (RequestClass: TReqGetVersion; ResponseClass: TResGetVersion;)
);
I have a private field on my WebModule:
FResponse: TResBase; // Response object
In the WebmoduleBeforeDispatch, FWebAct is a ttgetlist, and I do:
lResponseClass := cWebActStructures[FWebAct].ResponseClass;
FResponse := lResponseClass.Create(lResponseClass.ClassName);
// FResponse.ClassName is TResGetList, OK
In the OnAction handler for the TWebActionItem that handles the 'ttgetlist', here is the essential part:
var
lListArr : TResIDNameArr;
begin
lListArr := (FResponse as TResGetList).activities;
with AClientDataSet do
begin
Open;
SetLength(lListArr,RecordCount); // Pre-allocate (currently 152)
l := 0;
First;
while not EOF do
begin
if SomeConditionMet then
begin
lListArr[l].id := FieldByName(lIDVeld).AsInteger;
lListArr[l].name := FieldByName(SName).AsString;
Inc(l);
end;
Next;
end;
Close;
SetLength(lListArr,l+1); // Adjust (l is now 108)
end;
FResponse.success := 1;
lListArr shows correct data at this moment, object inspector shows:
((3, 'Activiteit 3'), (4, 'Activiteit 3.1'), (5, 'Activiteit 3.2'), (10, 'Activiteit 3.3'), (8, 'Activiteit 5'),...
In WebmoduleAfterDispatch:
var lJSO: ISuperObject;
begin
lJSO := FResponse.ToJson;
LJSO.AsString does have the correct fields (&type, success, ... activities, ... ), and e.g. success=1, but activities is empty:
{
"errormessage":"",
"success":1,
"projects":[],
"threadid":1556,
"type":"ttgetlistresult",
"customers":[],
"activities":[]
}
What am I overlooking? Is maybe the lListArr := (FResponse as TResGetList).activities in the OnAction handler 'not good enough'?
BTW Feel free to update the question title, this was the best I could come up with.
When you assign lListArr, the local variable receives a reference to the same array that the activities field references. However, when you call SetLength(lListArr, ...), that makes lListArr refer to a new array, completely separate from the one it referenced before.
If you want the field to refer to the same array, you need to assign it back to the field:
(FResponse as TResGetList).activities := lListArr;
You can make that assignment at any time after the length has been set. Changes the the contents of the array will be visible through either variable. Changing the length is not the same as changing the contents, though. Changing the length re-allocates the entire array and gives a new, unique array.

Tabs and colored lines in Listbox

I am using a Tabbed Listbox component that was written by Fredric Rylander back in 1999 and it has been serving me well since then. :) Can't seem to find him anymore.
I now have an application that needs both Tabbed Data and alternating colored lines in the Listbox.
I can include the Component here for perusal if desired.
I tried coloring the lines from here
http://delphi.about.com/cs/adptips2002/a/bltip0602_4.htm
But then it eats the Tabs, but I do get the alternating colored lines.
Can someone please show me how to incorporate the two.
Thanks
Here's the Component
unit myListBoxTabbed;
{
Copyright © 1999 Fredric Rylander
You can easily add a header control to this list box: drop a header
control onto the form (it's default align property is set to alTop, if
it's not--set it); then set the myTabbedListBox's aligned property
to alClient; now, add the following two events and their code.
1) HeaderControl's OnSectionResize event:
var
i, last: integer;
begin
last := 0;
for i:=0 to HeaderControl1.Sections.Count-1 do begin
last := last + HeaderControl1.Sections[i].Width;
myTabbedListBox1.TabStops[i] := last;
end;
end;
2) Main form's OnCreate event:
var
i, last: integer;
begin
last := 0;
for i:=0 to HeaderControl1.Sections.Count-1 do begin
last := last + HeaderControl1.Sections[i].Width;
myTabbedListBox1.TabStops[i] := last;
end;
for i:=HeaderControl1.Sections.Count to MaxNumSections do
myTabbedListBox1.TabStops[i] := 2000;
end;
To get tab characters into the list box items either use the
string list property editor in the Delphi GUI and press
Ctrl + Tab or add tab characters (#9) in strings as so:
myTabbedListBox1.Items.Add( Edit1.Text + #9 + Edit2.Text );
I hope you find this tutorial helpful! :^)
(!) This is not a retail product, it's a tutorial and don't claim to
meet a potential user's demands.
If you find anything that seems odd (or incorrect even) don't hesitate to
write me a line. You can communicate with me at fredric#rylander.nu.
The source is available for you to use, abuse, modify and/or improve.
Happy trails!
/ Fredric
___________________________________F_r_e_d_r_i_c__R_y_l_a_n_d_e_r__
fredric#rylander.nu : www.rylander.nu : 6429296#pager.mirabilis.com
"power to the source sharing community"
}
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TTabsArray = array[0..9] of integer;
type
TmyTabbedListBox = class( TListBox )
private
{ Private declarations }
fTabStops: TTabsArray;
function GetTabStops( iIndex: integer ): integer;
procedure SetTabStops( iIndex, iValue: integer);
function GetTabsString: string;
procedure SetTabsString( const sValue: string );
protected
{ Protected declarations }
procedure UpdateTabStops;
public
{ Public declarations }
procedure CreateParams( var cParams: TCreateParams ); override;
procedure CreateWnd; override;
property TabStops[ iIndex: integer ]: integer
read GetTabStops write SetTabStops;
published
{ Published declarations }
property TabsString: string
read GetTabsString write SetTabsString;
end;
procedure Register;
resourcestring
STR_ALPHA_UPPERLOWER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
CHAR_SEMICOLON = ';';
implementation
procedure Register;
begin
RegisterComponents('Additional', [TmyTabbedListBox]);
end;
{ myTabbedListBox }
procedure TmyTabbedListBox.CreateParams(var cParams: TCreateParams);
begin
inherited CreateParams( cParams );
// add the window style LBS_USETABSTOPS to accept tabs
cParams.Style := cParams.Style or LBS_USETABSTOPS;
end;
procedure TmyTabbedListBox.CreateWnd;
var
i: integer;
begin
inherited CreateWnd;
// set all the tabs into the box
for i := Low( fTabStops ) to High( fTabStops ) do
fTabStops[i] := i * 100;
// show the real tab positions
UpdateTabStops;
end;
function TmyTabbedListBox.GetTabsString: string;
var
sBuffer: string;
i: integer;
begin
// init var
sBuffer := SysUtils.EmptyStr;
// set all tabstops to the string (separated by ';'-char)
for i := Low( fTabStops ) to High( fTabStops ) do
sBuffer := sBuffer + IntToStr( fTabStops[i] ) + CHAR_SEMICOLON;
// and here we have the results
Result := sBuffer;
end;
function TmyTabbedListBox.GetTabStops( iIndex: integer ): integer;
begin
// nothing funny here
Result := fTabStops[iIndex];
end;
procedure TmyTabbedListBox.SetTabsString( const sValue: string );
var
sBuffer: string;
i, len: integer;
begin
// copy value into buffer
sBuffer := sValue;
// set the tabstops as specified
for i := Low( fTabStops ) to High( fTabStops ) do begin
len := Pos( sBuffer, CHAR_SEMICOLON );
fTabStops[i] := StrToIntDef( Copy( sBuffer, 1, len ), 0 );
Delete( sBuffer, 1, len );
end;
// show/redraw the results
UpdateTabStops;
Invalidate;
end;
procedure TmyTabbedListBox.SetTabStops( iIndex, iValue: integer );
begin
// do we really need to update?
if fTabStops[iIndex] <> iValue then begin
// oki, let's then
fTabStops[iIndex] := iValue;
// show/redraw the results
UpdateTabStops;
Invalidate;
end;
end;
procedure TmyTabbedListBox.UpdateTabStops;
var
i, iHUnits: integer;
arrConvertedTabs: TTabsArray;
begin
// convert dialog box units to pixels.
// dialog box unit = average character width/height div 4/8
// determine the horizontal dialog box units used by the
// list box (which depend on its current font)
Canvas.Font := Font;
iHUnits := Canvas.TextWidth( STR_ALPHA_UPPERLOWER ) div 52;
// convert the array of tab values
for i := Low( arrConvertedTabs ) to High( arrConvertedTabs ) do
arrConvertedTabs[i] := ( fTabStops[i] * 4 ) div iHUnits;
// activate the tabs stops in the list box,
// sending a Windows list box message
SendMessage( Handle, LB_SETTABSTOPS,
1 + High( arrConvertedTabs ) - Low( arrConvertedTabs ),
LongInt( #arrConvertedTabs ) );
end;
end.
Here's an example using a standard TListBox and it's OnDrawItem event, based on the code from the link you provided and tested in Delphi 2007. Note you need to set the ListBox.Style to lbOwnerDrawFixed. You can perhaps use this as a base for modifying the component (or just abandon it altogether).
procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
var
LB: TListBox;
NewColor: TColor;
NewBrush: TBrush;
R: TRect;
Fmt: Cardinal;
ItemText: string;
begin
NewBrush := TBrush.Create;
LB := (Control as TListBox);
if (odSelected in State) then
begin
NewColor := LB.Canvas.Brush.Color;
end
else
begin
if not Odd(Index) then
NewColor := clSilver
else
NewColor := clYellow;
end;
NewBrush.Style := bsSolid;
NewBrush.Color := NewColor;
// This is the ListBox.Canvas brush itself, not to be
// confused with the NewBrush we've created above
LB.Canvas.Brush.Style := bsClear;
R := Rect;
ItemText := LB.Items[Index];
Fmt := DT_EXPANDTABS or DT_CALCRECT or DT_NOCLIP;
DrawText(LB.Canvas.Handle, PChar(ItemText), Length(ItemText),
R, Fmt);
// Note we need to FillRect on the original Rect and not
// the one we're using in the call to DrawText
Windows.FillRect(LB.Canvas.Handle, Rect, NewBrush.Handle) ;
DrawText(LB.Canvas.Handle, PChar(ItemText), Length(ItemText),
R, DT_EXPANDTABS);
NewBrush.Free;
end;
Here's the output of the above code:

Accessing OleVariant containing VT_ARRAY of VT_RECORD from Delphi

Using Delphi, I need to access a OleVariant containing one or more records in an array.
The method I call returns a VT_ARRAY of VT_RECORD, and the records themselves are defined as:
struct StreamTimeInfo {
unsigned int PID;
LONGLONG PTS;
LONGLONG TimeStamp;
};
My code is like this:
procedure Test;
type
TStreamInfo = record
PID: Cardinal;
PTS: Int64;
TimeStamp: Int64;
end;
var
Value: OleVariant
StreamTime: TStreamInfo;
begin
GetValue(Value); // Value holds a VT_ARRAY of VT_RECORD
// How should I access the array of records in Delphi?
// I've tried this to get to the first element:
StreamTime := TStreamInfo(TVarData(Value).VPointer^);
end;
I do not understand how to access the records from Delphi.
Any input is greatly appreciated.
I've never done this before, but I think this should work.
type
TStreamInfoArray = array [0..MaxArrayCount-1] of TStreamInfo;
PStreamInfoArray = ^TStreamInfoArray;
var
Value: Variant;
p: PStreamInfoArray;
StreamInfo: TStreamInfo;
begin
GetValue(Value);
p := PStreamInfoArray(VarArrayLock(Value));
try
StreamInfo := p^[Index];
finally
VarArrayUnlock(Value);
end;
end;
For future reference and for others, here is the final working code:
// Original C-Source definition of StreamTimeInfo
// import "oaidl.idl";
// import "ocidl.idl";
// [uuid(A5AA2ACD-BEA0-4570-9232-D8301A6DAE0F)]
// struct StreamTimeInfo {
// unsigned int PID;
// LONGLONG PTS;
// LONGLONG TimeStamp;
// };
// cpp_quote("typedef struct StreamTimeInfo StreamTimeInfo;")
procedure GetStreamTimes;
type
TStreamTimeInfo = record
PID: Cardinal;
PTS: Int64;
TimeStamp: Int64;
end;
TStreamTimeInfoArray = array[0..31] of TStreamTimeInfo;
PStreamTimeInfoArray = ^TStreamTimeInfoArray;
var
Value: OleVariant;
SizeOfArray: Integer;
PtrToArray: PStreamTimeInfoArray;
begin
GetValue(EMPGPDMX_STREAMTIMES, Value);
if VarArrayDimCount(Value) = 1 then
begin
SizeOfArray := 1 + VarArrayHighBound(Value, 1) - VarArrayLowBound(Value, 1);
PtrToArray := PStreamTimeInfoArray(VarArrayLock(Value));
try
for I := 0 to SizeOfArray - 1 do
begin
StreamTimeInfo := PtrToArray^[I];
// Usage Sample:
// FStatus.StreamTimePID[I] := StreamTimeInfo.PID;
// FStatus.StreamTimePTS[I] := StreamTimeInfo.PTS;
// FStatus.StreamTimeTS[I] := StreamTimeInfo.TimeStamp;
end;
finally
VarArrayUnlock(Value);
end;
end;
end;

Is there any way to get all the controls on a container control?

I've got a form with a bunch of controls on it, and I wanted to iterate through all the controls on a certain panel and enable/disable them.
I tried this:
var component: TComponent;
begin
for component in myPanel do
(component as TControl).Enabled := Value;
end;
But that did nothing. Turns out all components are in the form's component collection, not their parent object's. So does anyone know if there's any way to get all the controls inside a control? (Besides an ugly workaround like this, which is what I ended up having to do):
var component: TComponent;
begin
for component in myPanel do
if (component is TControl) and (TControl(component).parent = myPanel) then
TControl(component).Enabled := Value;
end;
Someone please tell me there's a better way...
You're looking for the TWinControl.Controls array and the accompanying ControlCount property. Those are for a control's immediate children. To get grandchildren etc., use standard recursive techniques.
You don't really want the Components array (which is what the for-in loop iterates over) since it has nothing to do, in general, with the parent-child relationship. Components can own things that have no child relationship, and controls can have children that they don't own.
Also note that disabling a control implicitly disables all its children, too. You cannot interact with the children of a disabled control; the OS doesn't send input messages to them. To make them look disabled, though, you'll need to disable them separately. That is, to make a button have grayed text, it's not enough to disable its parent, even though the button won't respond to mouse clicks. You need to disable the button itself to make it paint itself "disabledly."
If you disable a panel, al controls on it are disabled too.
Recursive solution with anonymous methods:
type
TControlProc = reference to procedure (const AControl: TControl);
procedure TForm6.ModifyControl(const AControl: TControl;
const ARef: TControlProc);
var
i : Integer;
begin
if AControl=nil then
Exit;
if AControl is TWinControl then begin
for i := 0 to TWinControl(AControl).ControlCount-1 do
ModifyControl(TWinControl(AControl).Controls[i], ARef);
end;
ARef(AControl);
end;
procedure TForm6.Button1Click(Sender: TObject);
begin
ModifyControl(Panel1,
procedure (const AControl: TControl)
begin
AControl.Enabled := not Panel1.Enabled;
end
);
end;
Here is a Delphi 2007 way:
procedure TForm6.ModifyControl(const AControl: TControl; const value: Boolean);
var
i: Integer;
begin
if AControl=nil then Exit;
if AControl is TWinControl then begin
for i := 0 to TWinControl(AControl).ControlCount-1 do
ModifyControl(TWinControl(AControl).Controls[i], value);
end;
Acontrol.Enabled := value;
end;
procedure TForm6.Button1Click(Sender: TObject);
begin
ModifyControl(Panel1, true); // true or false
end;
Simply
Panel.Enabled := Value;
This one finds all controls, also nested in frames etc, and points to them via the list.
Be aware to free the list afterwards.
Function AllControls(form : tForm) : tList<tControl>;
Procedure Add(Control : tControl );
var i : integer;
begin
if Control is TWinControl then
with TWinControl(Control) do
for i := 0 to Controlcount-1 do
Add(Controls[i]);
if Control <> form then
result.Add(Control);
end;
begin
result := tlist<tControl>.create;
add(form);
end;
var contrls : tlist<tcontrol>;
c : tcontrol;
begin
try
contrls := AllControls(form1);
for c in ctrls do Visit(c); // Do something
finally
contrls.free;
end;
end;
And if you want a generic version, where you can ask for a specific control type, you can use this:
Procedure TForm1.Addcontrols( control : tcontrol; list : tlist<tcontrol>);
var i : integer;
begin
if control is twincontrol then
with twincontrol(control) do
for i := 0 to controlcount-1 do
addControl(controls[i], list);
list.Add(control)
end;
Function TForm1.GetControls<T>(f : tform) : tlist<T>;
var list : tlist<tcontrol>;
c : tcontrol;
begin
list := tlist<tcontrol>.Create;
addControls(f, list);
result := tlist<t>.create;
for c in list do
if c <> f then
if c is t then
result.Add(c);
list.free;
end;
procedure TForm1.FormCreate(Sender: TObject);
VAR List : TList<TRadioButton>;
begin
List := GetControls<TRadioButton>(self);
end;
end.
Use
List := GetControls<TControl>(self);
to get all controls..
I know this post is a little old but I came here based on a search for the same information. Here is some C++ code that I worked out for anyone interested.
// DEV-NOTE: GUIForm flattens the VCL controls
// VCL controls are nested. I.E. Controls on a
// Panel would have the Panel as a parent and if
// that Panel is on a TForm, TForm's control count
// does not account for the nested controls on the
// Panel.
//
// GUIControl is passed a Form pointer and an index
// value, the index value will walk the controls on the
// form and any child controls counting up to the idx
// value passed in. In this way, every control has a
// unique index value
//
// You can use this to iterate over every single control
// on a form. Here is example code:
//
// int count = 0;
// TForm *pTForm = some_form
// TControl *pCtrl = 0;
// do
// {
// pCtrl = GUIControl(pTForm, count++);
//
// }while(pCtrl);
TControl *GUIControl(TForm *F, int idx)
{
TControl *rval = 0;
int RunCount = 0;
for(int i=0; i<F->ControlCount && !rval; i++)
{
TControl *pCtl = F->Controls[i];
if(RunCount == idx )
rval = pCtl;
else
rval = GUIChildControl( pCtl, RunCount, idx);
RunCount++;
}
return(rval);
}
TControl *GUIChildControl(TControl *C, int &runcount, int idx)
{
TControl *rval = 0;
TWinControl *pC = dynamic_cast<TWinControl *>(C);
if(pC)
{
for(int i=0; i<pC->ControlCount && !rval; i++)
{
TControl *pCtrl = pC->Controls[i];
runcount++;
if( runcount == idx)
rval = pCtrl;
else
{
TWinControl *pCC = dynamic_cast<TWinControl *>(pCtrl);
if(pCC)
{
if( pCC->ControlCount )
rval = GUIChildControl(pCtrl, runcount, idx);
}
}
}
}
return(rval);
}

Resources