How can I read values' names and data from the registry? - delphi

I have stored a list of items from gridView into the registry as below:
frmPOSConfigSet.tblCatItems.First;
while not frmPOSConfigSet.tblCatItems.Eof do
begin
RegItemSetting.WriteString('stk Group\Candy',
frmPOSConfigSet.tblCatItemsCODE.Text,
frmPOSConfigSet.tblCatItemsSPHOTO.Text);
frmPOSConfigSet.tblCatItems.Next;
end;
In Registry Editor, I have this:
stk Group
- Candy
-> YUPI_GUM_HB , c:\Users\chai\Pictures\POS Item Images\image1.jpg
-> YUPI_GUM_SBKISS , c:\Users\chai\Pictures\POS Item Images\image2.jpg
After I close the form and open it again, all values in gridView are gone. How can I retrieve the ident (eg. YUPI_GUM_HB) and its value (eg.c:\Users\chai\Pictures\POS Item Images\image1.jpg) from the registry and put it in the gridView when I load the form?

It doesn't quite look like you're using TRegistry (too many parameters to WriteString), but if you were, you could use it to get everything back out of the registry. I suspect you're stuck because you want to call ReadString, but you don't know the registry values' names, so you don't know what to pass into ReadString.
You can get a list of all the values' names by calling GetValueNames. Pass it a TStringList (or any other TStrings descendant), and that method will fill the list with all the value names.
var
Names: TStrings;
Name, Data: string;
i: Integer;
begin
RegItemSetting.OpenKeyReadOnly('stk Group\Candy');
Names := TStringList.Create;
try
RegItemSetting.GetValueNames(Names);
for i := 0 to Pred(Names.Count) do begin
Name := Names[i];
Data := RegItemSetting.ReadString(Name);
end;
finally
Names.Free;
end;
end;

Related

Delphi 10.4 - Sort Memtable by clicking Stringgrid Header

I feel like an idiot because my question seams so simple but I don't get it done :D
My Settings is that:
One Dataset (Memtable), One Stringgrid. The Grid is bind via live Bindungs.
I would like to sort my Columns by clicking on the GridHeader. In the OnHeaderClick Event I get an tColumn Object. I only can read the Column.Header String, but I changed the Text from the Header to a more speakable Text. When I put Column.header into Memtable.Indexfieldsname Memtable says that field does not exist, what is right, but I don't know how to get the right Fieldname from the column.
What you want is quite straightforward to do. In the example below, which uses the demo data from
the Biolife demo, I've linked the StringgRid to the FDMemTable entirely by binding objects
created in code so that there is no doubt about any of the binding steps or binding properties,
nor the method used to establish the bindings.
procedure TForm2.FormCreate(Sender: TObject);
var
BindSourceDB1 : TBindSourceDB;
LinkGridToDataSourceBindSourceDB1 : TLinkGridToDataSource;
begin
// Note : You need to load FDMemTable1 at design time from the sample Biolife.Fds datafile
// The following code creates a TBindSourceDB which Live-Binds FDMemTable1
// to StringGrid1
//
// As a result, the column header texts will be the fieldnames of FDMemTable1's fields
// However, the code that determines the column on which to sort the StringGrid does not depend
// on this
BindSourceDB1 := TBindSourceDB.Create(Self);
BindSourceDB1.DataSet := FDMemTable1;
LinkGridToDataSourceBindSourceDB1 := TLinkGridToDataSource.Create(Self);
LinkGridToDataSourceBindSourceDB1.DataSource := BindSourceDB1;
LinkGridToDataSourceBindSourceDB1.GridControl := StringGrid1;
end;
procedure TForm2.StringGrid1HeaderClick(Column: TColumn);
// Sorts the STringGrid on the column whose header has been clicked
var
ColIndex,
FieldIndex : Integer;
AFieldName : String;
begin
ColIndex := Column.Index;
FieldIndex := ColIndex;
AFieldName := FDMemTable1.Fields[FieldIndex].FieldName;
Caption := AFieldName;
// Should check here that the the field is a sortable one and not a blob like a graphic field
FDMemTable1.IndexFieldNames := AFieldName;
end;
Note that this answer assumes that there is a one-for-one correspondence between grid columns and fields of the bound dataset, which will usually be the case for bindings created using the default methods in the IDE. However, Live Binding is sophisticated enough to support situations where this correspondence does not exist, and in those circumstances it should not be assumed that the method in this answer will work.

How to correctly use IFileOperation in Delphi to delete the files in a folder

I'm trying to create a simple example of using IFileOperation to delete the files in a
given directory, to include in the answer to another q for comparison with other methods.
Below is the code of my MRE. It
successfully creates 1000 files in a subdirectory off C:\Temp and then attempts to delete
them in the DeleteFiles method. This supposedly "easy" task fails but I'm not sure
exactly where it comes off the rails. The comments in the code show what I'm expecting
and the actual results. On one occasion, instead of the exception noted, I got a pop-up
asking for confirmation to delete an item with an odd name which was evidently an array of
numbers referring to a shell item, but my attempt to capture it using Ctrl-C failed;
I'm fairly sure I'm either missing a step or two, misusing the interfaces involved
or both. My q is, could anybody please show the necessary corrections to the code to get IFileOperation.DeleteItems() to delete the files in question, as I am completely out of my depth with this stuff? I am not interested in alternative methods of deleting these files, using the shell interfaces or otherwise.
procedure TForm2.DeleteFiles;
var
iFileOp: IFileOperation;
iIDList : ItemIDList;
iItemArray : IShellItemArray;
iArray : Array[0..1] of ItemIDList;
Count : DWord;
begin
iFileOp := CreateComObject(CLSID_FileOperation) as IFileOperation;
iIDList := ILCreateFromPath(sPath)^;
// IFileOperation.DeleteItems seems to require am IShellItemArray, so the following attempts
// to create one
// The definition of SHCreateShellItemArrayFromIDLists
// seems to require a a zero-terminated array of ItemIDLists so the next steps
// attempt to create one
ZeroMemory(#iArray, SizeOf(iArray));
iArray[0] := iIDList;
OleCheck(SHCreateShellItemArrayFromIDLists(1, #iArray, iItemArray));
// Next test the number of items in iItemArray, which I'm expecting to be 1000
// seeing as the CreateFiles routine creats that many
OleCheck(iItemArray.GetCount(Count));
Caption := IntToStr(Count); // Duh, this shows Count to be 1, not the expected 1000
OleCheck(iFileOp.DeleteItems(iItemArray));
OleCheck( iFileOp.PerformOperations );
// Returns Exception 'No object for moniker'
end;
procedure TForm2.Button1Click(Sender: TObject);
begin
DeleteFiles;
end;
procedure CreateFiles;
var
i : Integer;
SL : TStringList;
FileName,
FileContent : String;
begin
SL := TStringList.Create;
try
if not (DirectoryExists(sPath)) then
MkDir(sPath);
SL.BeginUpdate;
for i := 0 to 999 do begin
FileName := Format('File%d.Txt', [i]);
FileContent := Format('content of file %s', [FileName]);
SL.Text := FileContent;
SL.SaveToFile(sPath + '\' + FileName);
end;
SL.EndUpdate;
finally
SL.Free;
end;
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
CreateFiles;
end;
You are leaking the memory returned by ILCreateFromPath(), you need to call ILFree() when you are done using the returned PItemIDList.
Also, you should not be dereferencing the PItemIDList. SHCreateShellItemArrayFromIDLists() expects an array of PItemIDList pointers, but you are giving it an array of ItemIDList instances.
Try this instead:
procedure TForm2.DeleteFiles;
var
iFileOp: IFileOperation;
iIDList : PItemIDList;
iItemArray : IShellItemArray;
Count : DWord;
begin
iFileOp := CreateComObject(CLSID_FileOperation) as IFileOperation;
iIDList := ILCreateFromPath(sPath);
try
OleCheck(SHCreateShellItemArrayFromIDLists(1, #iIDList, iItemArray));
finally
ILFree(iIDList);
end;
// Next test the number of items in iItemArray, which I'm expecting to be 1000
// seeing as the CreateFiles routine creates that many
OleCheck(iItemArray.GetCount(Count));
Caption := IntToStr(Count); // Duh, this shows Count to be 1, not the expected 1000
OleCheck(iFileOp.DeleteItems(iItemArray));
OleCheck( iFileOp.PerformOperations );
// Returns Exception 'No object for moniker'
end;
That being said, even if this were working correctly, you are not creating an IShellItemArray containing 1000 IShellItems for the individual files. You are creating an IShellItemArray containing 1 IShellItem for the C:\Temp subdirectory itself.
Which is fine if your goal is to delete the whole folder. But in that case, I would suggest using SHCreateItemFromIDList() or SHCreateItemFromParsingName() instead, and then pass that IShellItem to IFileOperation.DeleteItem().
But, if your goal is to delete the individual files without deleting the subdirectory as well, then you will have to either:
get the IShellFolder interface for the subdirectory, then enumerate the relative PIDLs of its files using IShellFolder.EnumObjects(), and then pass the PIDLs in an array to SHCreateShellItemArray().
get the IShellFolder interface of the subdirectory, then query it for an IDataObject interface using IShellFolder.GetUIObjectOf(), and then use SHCreateShellItemArrayFromDataObject(), or just give the IDataObject directly to IFileOperation.DeleteItems().
get an IShellItem interface for the subdirectory, then query its IEnumShellItems interface using IShellItem.BindToHandler(), and then pass that directly to IFileOperation.DeleteItems().

Accessing TListItem.Data leads to an error when using records

I have a program which uses TListView to visualize and store some data. TListitem's data property is filled with a pointer to a record like so:
type
TWatch = record
name : string;
path : string;
//...
end;
procedure TfrmProcessWatcherMain.AddWatchToListView(AWatch: TWatch);
var
ANewWatch : TListItem;
begin
ANewWatch := lvWatches.Items.Add; //lvWatches is TListview
//...
ANewWatch.Data:= #AWatch;
end;
When I'm trying to retrieve this data somehow I'm getting access violation error, which is a total surprise for me because all seems legit, here is code of retrieval:
if(lvWatches.Selected <> nil) then begin
tempWatch := TWatch(lvWatches.Selected.Data^); // AV here
ShowMessage(tempWatch.name);
Also AWatch which is passed to a first function is stored in a
WatchList : TList<TWatch>;
so it is accessible using other methods
The problem is that #AWatch is the address of a local variable. As soon as AddWatchToListView returns AWatch goes out of scope and that address is no longer valid.
Instead of taking the address of a local variable you need to allocate a record on the heap using New.
procedure TfrmProcessWatcherMain.AddWatchToListView(AWatch: TWatch);
var
ANewWatch : TListItem;
P : ^TWatch;
begin
ANewWatch := lvWatches.Items.Add;
New(P);
P^ := AWatch;
ANewWatch.Data:= P;
end;
You will need to deallocate the memory with Dispose whenever a list item is destroyed. Do that using the list view's OnDeletion event.
Alternatively, you could store the index of the item in WatchList. Or the address of the record in WatchList, which you get like this: #WatchList.List[Index]. Both of these options rely on WatchList not being modified after references to items are taken, which may be too constraining for you.

Assigning the selected items in a TxpComboBox to a variable

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.

How to store dynamic arrays in a TList?

I need to store an unknown number of groups. Each group has an unknown number of elements/items.
This is my 'group':
TGroup= array of Integer; <------ dynamic array (as you can see) :)
I want to use a TList to hold my groups. The idea is that I may want to access the groups later and add more items to them.
I have this code, but I can't make it work:
TYPE
TGroup= array of Integer; // Each group has x items (x can be from 1 to 10000)
procedure TForm1.FormCreate(Sender: TObject);
VAR CurGroup: TGroup;
grp, item: Integer;
Groups: TList; // can contain up to 1 million groups
begin
Groups:= TList.Create;
{ Init }
for grp:= 1 to 4 DO // Put a dummy item in TList
begin
SetLength(CurGroup, 1); // Create new group
Groups.Add(#CurGroup); // Store it
end;
CurGroup:= NIL; // Prepare for next use
for grp:= 1 to 4 DO // We create 4 groups. Each group has 3 items
begin
CurGroup:= Groups[Groups.Count-1]; // We retrieve the current group from list in order to add more items to it
{ We add few items }
for item:= 0 to 2 DO
begin
SetLength(CurGroup, Length(CurGroup)+1); // reserve space for each new item added
CurGroup[item]:= Item;
end;
Groups[Groups.Count-1]:= #CurGroup; // We put the group back into the list
end;
{ Verify }
CurGroup:= NIL;
CurGroup:= Groups[0];
Assert(Length(CurGroup)> 0); // FAIL
if (CurGroup[0]= 0)
AND (CurGroup[1]= 1)
AND (CurGroup[2]= 2)
then Application.ProcessMessages;
FreeAndNil(Groups);
end;
Note: The code is complete. You can paste it in your Delphi (7) to try it.
Oh, this would be sooooo much nicer in newer versions of Delphi... You'd use the generic, TList<T>. var Groups: TList<TGroup>;
You're best bet is to use another dynamic array: Groups: array of TGroup;
The reason is that dynamic arrays are compiler managed and reference counted. TList only operates on Pointers. There is no straight forward way to keep the reference counts sane when trying to put the dynarrays into the TList.
Another issue you're having is that you're adding the stack address of the dynamic array variable to the TList, and not the actual array. The expression #CurGroup is the "address of the CurGroup variable" which being a local variable, is on the stack.
I've created a wrapper around dynamic array RTTI.
It's just a first draft, but it was inspired by your question, and the fact that the TList methods are missing.
type
TGroup: array of integer;
var
Group: TGroup;
GroupA: TDynArray;
i, v: integer;
begin
GroupA.Init(TypeInfo(TGroup),Group); // associate GroupA with Group
for i := 0 to 1000 do begin
v := i+1000; // need argument passed as a const variable
GroupA.Add(v);
end;
v := 1500;
if GroupA.IndexOf(v)<0 then // search by content
ShowMessage('Error: 1500 not found!');
for i := GroupA.Count-1 downto 0 do
if i and 3=0 then
GroupA.Delete(i); // delete integer at index i
end;
This TDynArray wrapper will work also with array of string or array of records... Records need only to be packed and have only not reference counted fields (byte, integer, double...) or string reference-counted fields (no Variant nor Interface within).
The IndexOf() method will search by content. That is e.g. for an array of record, all record fields (including strings) must match.
See TDynArray in the SynCommons.pas unit from our Source Code repository. Works from Delphi 6 up to XE, and handle Unicode strings.
And the TTestLowLevelCommon._TDynArray method is the automated unitary tests associated with this wrapper. You'll find out here samples of array of records and more advanced features.
I'm currently implementing SaveToStream and LoadToStream methods...
Perhaps a new way of using generic-like features in all Delphi versions.
Edit:
I've added some new methods to the TDynArray record/object:
now you can save and load a dynamic array content to or from a string (using LoadFromStream/SaveToStream or LoadFrom/SaveTo methods) - it will use a proprietary but very fast binary stream layout;
and you can sort the dynamic array content by two means: either in-place (i.e. the array elements content is exchanged) or via an external integer index look-up array (using the CreateOrderedIndex method - in this case, you can have several orders to the same data);
you can specify any custom comparison function, and there is a new Find method will can use fast binary search if available.
Here is how those new methods work:
var
Test: RawByteString;
...
Test := GroupA.SaveTo;
GroupA.Clear;
GroupA.LoadFrom(Test);
GroupA.Compare := SortDynArrayInteger;
GroupA.Sort;
for i := 1 to GroupA.Count-1 do
if Group[i]<Group[i-1] then
ShowMessage('Error: unsorted!');
v := 1500;
if GroupA.Find(v)<0 then // fast binary search
ShowMessage('Error: 1500 not found!');
Still closer to the generic paradigm, faster, and for Delphi 6 up to XE...
I don't have D7 on a machine here, but you might try something like this instead (console test app - it compiles in XE with no hints or warnings, but not sure how D7 will handle it):
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes;
type
TGroup = array of Integer;
THolder=class(TObject)
Group: TGroup;
end;
var
GroupList: TList;
i: Integer;
begin
GroupList := TList.Create;
for i := 0 to 2 do
begin
GroupList.Add(THolder.Create);
with THolder(GroupList[GroupList.Count - 1]) do
begin
SetLength(Group, 3);
Group[0] := i;
Group[1] := i + 10;
Group[2] := i + 20;
end;
end;
for i := 0 to GroupList.Count - 1 do
begin
Writeln(i, ':0 ', THolder(GroupList[i]).Group[0]);
Writeln(i, ':1 ', THolder(GroupList[i]).Group[1]);
Writeln(i, ':2 ', THolder(GroupList[i]).Group[2]);
end;
// Not cleaning up list content, because we're exiting the app.
// In a real app, of course, you'd want to free each THolder in
// the list, or use a TObjectList and let it own the objects.
// You'd also want a try..finally to make sure the list gets freed.
GroupList.Free;
Readln;
end.
Another thing you may want to try is using a TObjectList. TObjectList is able to store a list of Objects, so you can create a new class descendant of TObject and then manage the TObjectList using these objects.
Something like (untested):
type TGroupArray : array of Integer;
type TGroup = class(Tobject)
GroupArray : TGroupArray;
end;
GroupList : TobjectList;
procedure TForm1.FormCreate(Sender: TObject);
var CurGroup : TGroup;
begin
GroupList:= TObjectList.Create;
CurGroup := TGroup.Create;
SetLength(CurGroup.GroupArray,1);
CurGroup.GroupArray[0] := 10;
GroupList.Add(CurGroup);
RetreiveGroup := GroupList.Items[0];
FreeandNil(GroupList);
end;
etc...
When you code:
for grp:= 1 to 4 DO // Put a dummy item in TList
begin
SetLength(CurGroup, 1); // Create new group
Groups.Add(#CurGroup); // Store it
end;
in fact, SetLength(CurGroup) WON'T create a new group.
It will resize the only one existing CurGroup.
So #CurGroup won't change: it will always be some address on the stack, where CurGroup is added. You add the same address to the list several times.
So You'll have to create a dynamic array of TGroup instances, like that:
var GroupArray: array of TGroup;
SetLength(GroupArray,4);
for grp := 0 to high(GroupArray) do
begin
SetLength(GroupArray[grp],1);
Groups.Add(#GroupArray[grp]);
end;
But of course, the GroupArray must remain allocated during all the time Groups will need it. So you'll have perhaps to put this GroupArray as a property in the class, because if you create this GroupArray on the stack, all GroupArray[] items will be released when the method exits and its stack is freed.
But of course, the GroupArray[] will be a more direct access to the TGroup items... Groups[i] will be equal to GroupArray[]... With no reference counting problem... Because e.g. if you resize for instance a TGroup item from its pointer in Groups[], I'm not sure that you won't have any memory leak...
So, basically everybody suggests to create an array of array instead of using a TList. Well, I already have done that. I just wanted to "upgrade" from 'array of array' to a TList since it has Add(). It looks like I will return to my original code.
Thank you all and +1 for each answer.

Resources