Assigning the selected items in a TxpComboBox to a variable - delphi

I'm attempting to assign all the selected items in a TxpListBox to a TStringList.
My initial thought was to do something like
Function AssignListBoxToList(ComponentName : TxpListBox) : Boolean;
var
slComponentValue : TStringList;
begin
slComponentValue := TStringList.Create;
slComponentValue.Add(ComponentName.Items);
end;
But it throws the following exception Incompatible types: 'String' and 'TString'.
Is there a way to either create a TStringList of TStrings, or is it safe to use String instead of TString in my TxpListBox, and/or am I missing something.
TxpListBox is a TListBox with a modified look to fit in with the Windows XP design aesthetic.

It looks like TxpComboBox.Items might be a TStrings descendent (like the standard TComboBox.Items). If that's the case, something like this should work:
slComponentValue := TStringList.Create;
slComponentValue.Add(ComponentName.Items[ComponentName.ItemIndex]);
Your function won't work as is, though, because it doesn't return slComponentValue.
It's generally not a good idea (without a specific reason to do so) to return an object from a function, because it's not clear where the responsibility lies to free it. I prefer to make that more clear by having a procedure accept an already-created instance of an object instead:
procedure AssignComboBoxToList(ComponentName : TxpComboBox;
ListToFill: TStrings) : Boolean;
begin
Assert(Assigned(ListToFill));
ListToFill.Add(ComponentName.Items[ComponentName.ItemIndex);
end;
You can then use it like this:
slComponentValue := TStringList.Create;
try
AssignComboBoxToList(YourComboBox, slComponentValue);
if slComponentValue.Count > 0 then
// Do whatever with the slComponentValue list
finally
slComponentValue.Free;
end;
However, as you're only dealing with a single string, it might be easier to just use a single string; there's not really a TStringList neededhere:
strResult := YourComboBox.Items[YourComboBox.ItemIndex];
With that being said, TComboBox doesn't support multiple selections; TListBox does, but TComboBox displays a drop down list and allows selecting of a single item, making your question somewhat unclear.

Related

Delphi - Can you close all of the database tables?

Can you close all database tables except some? Can you then reopen them? I use an absolute database that is similar to BDE. If this is possible, how can I do so many?
Yes, of course you can. You could iterate the Components property of your form/datamodule, use the is operator to check whether each is an instance of your table type and use a cast to call Open or Close on it.
The following closes all TABSDataSet tables on your form except one called Table1.
procedure TForm1.ProcessTables;
var
ATable : TABSDataSet; // used to access a particular TABSDataSet found on the form
i : Integer;
begin
for i := 0 to ComponentCount - 1 do begin
if Components[i] is TABSDataSet then begin
ATable := TABSDataSet(Components[i]);
// Now that you have a reference to a dataset in ATable, you can
// do whatever you like with it. For example
if ATable.Active and (ATable <> Table1) then
ATable.Close;
end;
end;
end;
I've seen from the code you've posted in comments and your answer that you
are obviously having trouble applying my code example to your situation. You
may find the following code easier to use:
procedure ProcessTables(AContainer : TComponent);
var
ATable : TABSTable;
i : Integer;
begin
for i := 0 to AContainer.ComponentCount - 1 do begin
if AContainer.Components[i] is TABSTable then begin
ATable := TABSTable(AContainer.Components[i]);
// Now that you have a reference to a dataset in ACDS, you can
// do whatever you like with it. For example
if ATable.Active then
ATable.Close;
end;
end;
end;
Note that this is a stand-alone procedure, not a procedure of a particular
form or datamodule. Instead, when you use this procedure, you call it passing
whatever form or datamodule contains the TABSTables you want to work with as the
AContainer parameter, like so
if Assigned(DataModule1) then
ProcessTables(DataModule1);
or
if Assigned(Form1) then
ProcessTables(Form1);
However, the downside of doing it this was is that it is trickier to specify which tables, if any, to leave open, because AContainer, being a TComponent, will not have any member tables.
Btw, your task would probably be easier if you could iterate through the tables in a TABSDatabase. However I've looked at its online documentation but can't see an obvious way to do this; I've asked the publishers, ComponentAce, about this but haven't had a reply yet.

Delphi - Getting property values with GetPropValue()

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;

How to replace TListbox Items property with my own published object list based type in a TCustomListBox control?

Overview
This question is a second attempt based on this one I recently asked: How can I make a TList property from my custom control streamable?
Although I accepted the answer in that question and it worked, I soon realized that TCollection is not the solution or requirement I was looking for.
Requirements
To keep my requirements as simple and clear to understand as possible, this is what I am attempting to:
Derive a new custom control based on TCustomListBox
Replace the Items property with my own Items type, eg a TList.
The TList (Items property) will hold objects, each containing a caption and a image index property etc.
Ownerdraw my listbox and draw its icons and text etc.
Create a property editor to edit the Items at design-time.
With that in mind, I know how to create the custom control, I know how to work with TList or even TObjectList for example, I know how to ownerdraw the control and I also know how to create the property editor.
Problem
What I don't know is how to replace the standard listbox Items type with my own? well I kind of do (publishing my own property that shares the same name), only I need to make sure it is fully streamable with the dfm.
I have searched extensively on this subject and have tried studying code where TListView and TTreeView etc publishes its Items type but I have found myself more confused than ever.
In fact I came across this very old question asked by someone else on a different website which asks very much what I want to do: Streaming a TList property of a component to a dfm. I have quoted it below in the event the link is lost:
I recently wrote a component that publishes a TList property. I then created a property editor for the TList to enable design-time editing. The problem is that the TList doesn't stream to the dfm file, so all changes are lost when the project is closed. I assume this is because TList inherits from TObject and not from TPersistant. I was hoping there was an easy work around for this situation (or that I have misunderstood the problem to begin with). Right now all I can come up with is to switch to a TCollection or override the DefineProperties method. Is there any other way to get the information in the TList streamed to and from the dfm?
I came across that whilst searching keywords such as DefineProperties() given that this was an alternative option Remy Lebeau briefly touched upon in the previous question linked at the top, it also seemed to be the answer to that question.
Question
I need to know how to replace the Items (TStrings) property of a TCustomListBox derived control with my own Items (TList) or Items (TObjectList) etc type but make it fully streamable with the dfm. I know from previous comments TList is not streamable but I cannot use TStrings like the standard TListBox control does, I need to use my own object based list that is streamable.
I don't want to use TCollection, DefineProperties sounds promising but I don't know how exactly I would implement this?
I would greatly appreciate some help with this please.
Thank you.
Override DefineProperties procedure in your TCustomListBox (let's name it TMyListBox here). In there it's possible to "register" as many fields as you wish, they will be stored in dfm in the same way as other fields, but you won't see them in object inspector. To be honest, I've never encountered having more then one property defined this way, called 'data' or 'strings'.
You can define 'normal' property or binary one. 'Normal' properties are quite handy for strings, integers, enumerations and so on. Here is how items with caption and ImageIndex can be implemented:
TMyListBox = class(TCustomListBox)
private
//other stuff
procedure ReadData(reader: TReader);
procedure WriteData(writer: TWriter);
protected
procedure DefineProperties(filer: TFiler); override;
//other stuff
public
//other stuff
property Items: TList read fItems; //not used for streaming, not shown in object inspector. Strictly for use in code itself. We can make it read-only to avoid memory leak.
published
//some properties
end;
that's DefineProperties implementation:
procedure TMyListBox.DefineProperties(filer: TFiler);
begin
filer.DefineProperty('data', ReadData, WriteData, items.Count>0);
end;
fourth argument, hasData is Boolean. When your component is saved to dfm, DefineProperties is called and it's possible to decide at that moment is there any data worth saving. If not, 'data' property is omitted. In this example, we won't have this property if there is no items present.
If we expect to ever use visual inheritance of this control (for example, create a frame with this listBox with predefined values and then eventually change them when put to form), there is a possibility to check, is value of this property any different than on our ancestor. Filer.Ancestor property is used for it. You can watch how it's done in TStrings:
procedure TStrings.DefineProperties(Filer: TFiler);
function DoWrite: Boolean;
begin
if Filer.Ancestor <> nil then
begin
Result := True;
if Filer.Ancestor is TStrings then
Result := not Equals(TStrings(Filer.Ancestor))
end
else Result := Count > 0;
end;
begin
Filer.DefineProperty('Strings', ReadData, WriteData, DoWrite);
end;
This would save a little bit of space (or lots of space if image is stored within) and sure is elegant, but in first implementation it can well be omitted.
Now the code for WriteData and ReadData. Writing is much easier usually and we may begin with it:
procedure TMyListBox.WriteData(writer: TWriter);
var i: Integer;
begin
writer.WriteListBegin; //in text dfm it will be '(' and new line
for i:=0 to items.Count-1 do begin
writer.WriteString(TListBoxItem(items[I]).caption);
writer.WriteInteger(TListBoxItem(items[I]).ImageIndex);
end;
writer.WriteListEnd;
end;
In dfm it will look like this:
object MyListBox1: TMyListBox
data = (
'item1'
-1
'item2'
-1
'item3'
0
'item4'
1)
end
Output from TCollection seems more elegant to me (triangular brackets and then items, one after another), but what we have here would suffice.
Now reading it:
procedure TMyListBox.ReadData(reader: TReader);
var item: TListBoxItem;
begin
reader.ReadListBegin;
while not reader.EndOfList do begin
item:=TListBoxItem.Create;
item.Caption:=reader.ReadString;
item.ImageIndex:=reader.ReadInteger;
items.Add(item); //maybe some other registering needed
end;
reader.ReadListEnd;
end;
That's it. In such a way rather complex structures can be streamed with ease, for example, two-dimensional arrays, we WriteListBegin when writing new row and then when writing new element.
Beware of WriteStr / ReadStr - these are some archaic procedures which exist for backward compatibility, ALWAYS use WriteString / ReadString instead!
Other way to do is to define binary property. That's used mostly for saving images into dfm. Let's say, for example, that listBox has hundreds of items and we'd like to compress data in it to reduce size of executable. Then:
TMyListBox = class(TCustomListBox)
private
//other stuff
procedure LoadFromStream(stream: TStream);
procedure SaveToStream(stream: TStream);
protected
procedure DefineProperties(filer: TFiler); override;
//etc
end;
procedure TMyListBox.DefineProperties(filer: TFiler);
filer.DefineBinaryProperty('data',LoadFromStream,SaveToStream,items.Count>0);
end;
procedure TMyListBox.SaveToStream(stream: TStream);
var gz: TCompressionStream;
i: Integer;
value: Integer;
item: TListBoxItem;
begin
gz:=TCompressionStream.Create(stream);
try
value:=items.Count;
//write number of items at first
gz.Write(value, SizeOf(value));
//properties can't be passed here, only variables
for i:=0 to items.Count-1 do begin
item:=TListBoxItem(items[I]);
value:=Length(item.Caption);
//almost as in good ol' Pascal: length of string and then string itself
gz.Write(value,SizeOf(value));
gz.Write(item.Caption[1], SizeOf(Char)*value); //will work in old Delphi and new (Unicode) ones
value:=item.ImageIndex;
gz.Write(value,SizeOf(value));
end;
finally
gz.free;
end;
end;
procedure TMyListBox.LoadFromStream(stream: TStream);
var gz: TDecompressionStream;
i: Integer;
count: Integer;
value: Integer;
item: TListBoxItem;
begin
gz:=TDecompressionStream.Create(stream);
try
gz.Read(count,SizeOf(count)); //number of items
for i:=0 to count-1 do begin
item:=TListBoxItem.Create;
gz.Read(value, SizeOf(value)); //length of string
SetLength(item.caption,value);
gz.Read(item.caption[1],SizeOf(char)*value); //we got our string
gz.Read(value, SizeOf(value)); //imageIndex
item.ImageIndex:=value;
items.Add(item); //some other initialization may be needed
end;
finally
gz.free;
end;
end;
In dfm it would look like this:
object MyListBox1: TMyListBox1
data = {
789C636260606005E24C86128654865C064386FF40802C62C40002009C5607CA}
end
78 is sort of signature of ZLib, 9C means default compression, so it works (there are only 2 items actually, not hundreds). Of course, this is just one example, with BinaryProperties any possible format may be used, for example saving to JSON and putting it into stream, or XML or something custom. But I'd not recommend to use binary unless it's absolutely inevitable, because it's difficult to see from dfm, what happens in component.
It seems like good idea to me to actively use streaming when implementing component: we can have no designer at all and set all values by manually editing dfm and see if component behaves correctly. Reading/loading itself can be tested easily: if component is loaded, then saved and text is just the same, it's all right. It's so 'transparent' when streaming format is 'human-readable', self-explaining that it often overweighs drawbacks (like file size) if there are any.

Can I use generics to do the same operation on similar types of controls?

I am using Delphi 2010 and I have a unit where over the years I have added my own procedures and functions that can be used with any project I make, such as:
function ListBoxIsSelected(ListBox: TListBox): Boolean;
begin
Result:= ListBox.ItemIndex <> -1;
end;
The above uses TListBox as a parameter, so whenever the above function is used I must supply a listbox that is of TListBox class.
Now suppose I have some other component libraries that could work with the same function, For example the Jedi component classes.
How could I use the above function, when the Jedi listbox is TJvListBox class and my function is looking for TListBox class? Although both components are practically the same, the class names are different. If I provided the same function specifically for the TJvListBox it would likely work because they are both "listboxes":
function ListBoxIsSelected(ListBox: TJvListBox): Boolean;
begin
Result:= ListBox.ItemIndex <> -1;
end;
Now, I have whole load of procedures and functions written in the same kind of way where I need to pass a component as a parameter. Having to rewrite them again just to work with a different component class is not feasible!
How can I write this with generics?
You can't write that with generics, unless your target classes all descend from the same base class of course. (But then you wouldn't need generics for it.)
If you really want something that can check if the ItemIndex property on any object <> -1, though, you can do that with a different Delphi 2010 feature: extended RTTI.
uses
SysUtils, RTTI;
function IsSelected(item: TObject): boolean;
var
context: TRttiContext;
cls: TRttiType;
prop: TRttiProperty;
ItemIndex: integer;
begin
if item = nil then
raise Exception.Create('Item = nil');
context := TRttiContext.Create;
cls := context.GetType(item.ClassType);
prop := cls.GetProperty('ItemIndex');
if prop = nil then
raise Exception.Create('Item does not contain an ItemIndex property.');
ItemIndex := prop.GetValue(item).AsInteger;
result := ItemIndex <> -1;
end;
Careful, though. There's no compile-time type checking here, and this process is significantly slower than your original routine. You probably won't notice it, but if you call something like this in a tight loop, it will slow it down.
I don't understand how I can write this with Generics?
You can’t – not unless your component implements a common interface or inherits from a common base class with the standard ListBox, and that interface / base class offers the ItemIndex property.
In fact, this use-case isn’t such a great example of generics because using an interface or base class in the declaration would work just as well.
In this case, you can write two overloaded functions, one expecting TJvListBox and the other expecting TListBox.
In more complex cases this approach may not apply so well, but I think your case is simple enough for this solution.
I cannot look it up right now (on holiday, no Delphi), but don't TJvListBox and TListBox descend from a common ancestor (my guess would be: TCustomListBox)? In that case something like this should work:
interface
function TListBox_IsItemSelected(_ListBox: TCustomListBox): boolean;
implementation
function TListBox_IsItemSelected(_ListBox: TCustomListBox): boolean;
begin
Result := _ListBox.ItemIndex <> -1;
end;
Just in case ItemIndex (as I said: I cannot check right now) is protected in TCustomListBox, you can just use a typecast hack:
type
TListBoxHack = class(TCustomListBox)
end;
function TListBox_IsItemSelected(_ListBox: TCustomListBox): boolean;
begin
Result := TListBoxHack(_ListBox).ItemIndex <> -1;
end;
(I just thought I should mention this since the original question has already been answered: Using Generics does not help here.)

Delphi getting value of form components properties

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.

Resources