I'm trying to programatically check a custom checkbox in a treeview. What I mean by custom, is that I call the below code to enable these checkboxes:
SetWindowLong(TreeView1.Handle, GWL_STYLE, GetWindowLong(TreeView1.Handle, GWL_STYLE) or TVS_CHECKBOXES)
I tried the below code to check it, but it did not work.
Node := TreeView1.Selected;
TVItem.mask := TVIF_STATE;
TVItem.hItem := Node.ItemId;
TreeView_GetItem(Node.TreeView.Handle, TVItem);
TVItem.stateMask := TVIS_CHECKED;
TVItem.mask := TVIS_CHECKED;
Thanks in advance
You have your state mask wrong, there's no TVIS_CHECKED state mask (in fact there is no TVIS_CHECKED, more on that later). Check boxes are managed through an image list, this is explained in the documentation.
Additionally, of course, you have to call TreeView_SetItem:
const
TVIS_CHECKED = 2 shl 12;
var
Node: TTreeNode;
TVItem: TTVItem;
begin
Node := TreeView1.Selected;
if Assigned(Node) then begin
TVItem.mask := TVIF_STATE;
TVItem.hItem := Node.ItemId;
TreeView_GetItem(Node.TreeView.Handle, TVItem);
TVItem.stateMask := TVIS_STATEIMAGEMASK;
TVItem.state := TVIS_CHECKED;
TreeView_SetItem(Node.TreeView.Handle, TVItem);
end;
end;
Normally I'd advise to call TreeView_SetCheckState but the VCL has got a weird error in the translation of the macro.
This is the macro in the header:
#define TreeView_SetCheckState(hwndTV, hti, fCheck) \
TreeView_SetItemState(hwndTV, hti, INDEXTOSTATEIMAGEMASK((fCheck)?2:1), TVIS_STATEIMAGEMASK)
where
#define INDEXTOSTATEIMAGEMASK(i) ((i) << 12)
This is why the made-up TVIS_CHECKED is $2000, the define shifts 2 12 bits left when fCheck is true (2 is the index of the checked image, 1 is unchecked).
This is VCL's translation:
function TreeView_SetCheckState(hwndTV: HWND; hti: HTreeItem; fCheck: BOOL): UINT;
var
LState: UINT;
begin
if IndexToStateImageMask(Integer(fCheck)) = 0 then
LState := 1
else
LState := 2;
Result := TreeView_SetItemState(hwndTV, hti, LState, TVIS_STATEIMAGEMASK);
where IndexToStateImageMask is
Result := I shl 12;
Strangely, the VCL shifts fCheck 12 bits and then calls TreeView_SetItemState with a state that makes no sense for a state image mask (TVIS_FOCUSED (1), TVIS_SELECTED (2)).
This is XE2, I suggest to test the macro first if you are working with a later version.
What do you have TVIS_CHECKED defined as? It should be:
const
TVIS_CHECKED = $2000;
Then, assuming Value is a Boolean of whether or not the item is checked:
FillChar(TVItem, SizeOf(TVItem), 0);
TVItem.hItem := Node.ItemId;
TVItem.mask := TVIF_STATE;
TVItem.stateMask := TVIS_STATEIMAGEMASK;
if Value then
TVItem.state := TVIS_CHECKED
else
TVItem.state := TVIS_CHECKED shr 1;
TreeView_SetItem(Node.Handle, TVItem);
Related
I am dynamicaly (at run time) adding controls into a TScrollBox using myScrollBox.AddObject
Now I need to remove all the controls I added to put new ones.
I tryed myScrollBox.Controls.Clear but after I call that function, any control I add are not showing up.
(Warning: I'm new to delphi and Firemonkey)
Update 1
Here is how I add my objects (this is just a test function)
procedure TMainForm.TaskDetailsAdd;
var
btn1 : TButton;
intI : Integer;
count: Integer;
begin
scbTaskVariables.BeginUpdate;
count := 0;
for intI := 0 to 100 do
begin
btn1 := TButton.Create(self);
btn1.Text := 'Salut ' + IntToStr(intI);
btn1.Parent := scbTaskVariables;
btn1.OnClick := Button1Click;
btn1.Tag := intI * 10;
btn1.Position.Y := intI * 50;
btn1.Position.X := intI * 15;
scbTaskVariables.AddObject(btn1);
count := scbTaskVariables.ControlsCount;
end;
scbTaskVariables.EndUpdate;
end;
The funny thing is that if I place a break point on count := scbTaskVariables.ControlsCount
I can see that ControlsCount goes from 0 to 1 for the first control and then it stays to 1 for the others.
Update 2
I submitted QC#125440.
The inverse of AddObject is RemoveObject. Call ScrollBox.RemoveObject(aChildObject) for each child object that you wish to remove.
The alternative is to set the Parent property of the child object. Set it to ScrollBox to add it. Set it to nil to remove it. This is interchangeable with AddObject and RemoveObject. You can do it either way.
However, when you attempt to do this, just as your said, attempts to add new controls fail if you have removed controls earlier. This would appear to be a bug. Please submit a QC report.
I tested on XE6.
Try with:
myScrollBox.Content.DeleteChildren;
I have added this as an Answer but as there are bugs in FMX it should be considered as a workaround at this stage.
I spent some time on your problem about deleting your buttons, but also to tried to find out more about the bug. David was very quick to spot this and shows his experience.
Two of my findings were that (1) the AddObect() does not appear to work with the buttons, for some reason, they are not being seen as "Objects" but as "Components". (2) Also I found that creating btn1 with the "scrollBox" as its owner helped to achieve an adequate result.
I used 1 x TScrollbox, 2 x TButton and 4 x TLabel. The buttons left with their default name and the TScrollBox with Your default name. So you can just copy and paste. btn1 is made a private variable along with it's procedures.
procedure TMainForm.TaskDetailsAdd;
var
intI : Integer;
begin
label1.Text := IntToStr(scbTaskVariables.ComponentCount);
// Initial count = 1, Probably the scroll box.
if scbTaskVariables.ComponentCount >1 then
TaskDetailsDel; // Don't create Buttons with same Name if already exists.
scbTaskVariables.BeginUpdate;
for intI := 0 to 99 do
begin
Sleep(20); //Keeps the "Pressed Button" active to prove it is working
btn1 := TButton.Create(scbTaskVariables);
btn1.Parent := scbTaskVariables;
btn1.Position.Y := intI * 50;
btn1.Position.X := intI * 15;
btn1.Tag := intI * 10;
btn1.TabOrder := 10 + intI;
btn1.Name := 'MyBtn' + IntToStr(intI);
btn1.Text := 'Salut ' + IntToStr(intI);
btn1.OnClick := Button1Click;
if btn1.IsChild(scbTaskVariables) = true then
Label2.Text := 'True'
else // All this, proves buttons not seen as children.
Label2.Text := 'False';
scbTaskVariables.AddObject(btn1);
// AddObject() taken out as button is not seen as "FmxObject"
end;
scbTaskVariables.EndUpdate;
Label3.Text := IntToStr(scbTaskVariables.ComponentCount);
// Count now all created (includes ScrollBox).
Label4.Text := IntToStr(scbTaskVariables.ControlsCount);
end;
The "TaskDetailsDel" procedure was was quite easy once I had determined that I was really dealing with "Components"
procedure TMainForm.TaskDetailsDel;
var
intI : Integer;
count: Integer;
begin
label1.Text := '';
label2.Text := '';
label3.Text := '';
label4.Text := '';
for intI := 0 to 99 do
begin
Sleep(20); //Keeps the "Pressed Button" active to prove it is working
btn1 := TButton(scbTaskVariables.FindComponent('MyBtn' + IntToStr(intI)));
btn1.Parent := Nil;
FreeAndNil(btn1);
end;
Count := scbTaskVariables.ComponentCount;
Label1.Text := IntToStr(Count);
end;
Using the FindComponent line did the trick.
Press F1 and type the links into the URL Box; I found these interesting, especially seeing how TButton is derived in the VCL and FMX.
ms-help://embarcadero.rs_xe3/libraries/Vcl.StdCtrls.TButton.html
ms-help://embarcadero.rs_xe3/libraries/FMX.Controls.TButton.html
ms-help://embarcadero.rs_xe3/libraries/FMX.Types.TStyledControl.html
ms-help://embarcadero.rs_xe3/rad/Objects,_Components,_and_Controls.html
ms-help://embarcadero.rs_xe3/libraries/FMX.Types.TFmxObject.AddObject.html
The TScrollBox has by default 1 component that is of type TScrollContent that is responsible of the display of the other components. So if we delete him then nothing will be shown ever.
I created this little function to RemoveAllComponents inside the TScrollBox (Expect the TScrollContent):
procedure RemoveAllComponentsScrollBox(ScrollBox : TScrollBox);
var i : integer; Obj : TFmxObject;
begin
for I := ScrollBox.ComponentCount-1 downto 0 do
begin
if ((ScrollBox.Components[i] is TFmxObject) and not (ScrollBox.Components[i] is TScrollContent)) then
begin
Obj:=TFmxObject(ScrollBox.Components[i]);
Obj.Parent:=nil;
FreeAndNil(Obj);
end;
end;
end;
This method can be improved by recursivity
I am creating a number of dynamically created THTMLabels but after these are created,when I try to get it's height,it always return the default height value.
Here is my code:
for i := 0 to ASentencePtr^.MUS.Count - 1 do
begin
j := Random(slTemp.Count);
sSen := ASentencePtr^.MUS.Strings[StrToInt(slTemp.Strings[j])] + ' / ';
THTMLabel.Create(Self).Name := 'lblSen_' + slTemp.Strings[j];
with THTMLabel(FindComponent('lblSen_' + slTemp.Strings[j])) do
begin
Font.Size := 18;
Font.Style := [fsBold];
Parent := FlowPanel1;
Width := Parent.Width;
Cursor := crHandPoint;
DragMode := dmAutomatic;
ControlStyle := ControlStyle + [csDisplayDragImage];
HTMLText.Add(sSen);
Autosizing := True;
end;
slTemp.Delete(j);
end;
Now when I try to access THTMLabel(FindComponent('lblSen_0')).Height, it returns only the default value which is 17. Where have I gone wrong? Any thoughts anyone? Any help is greatly appreciated, thanks.
We had the same problems but managed to solve them with the THTMLStaticText component and this function that calculates the height when dynamically (height) adjusted:
function CalculateDynamicHeight( aLabel: TLabel; htmlStaticText: THTMLStaticText): Integer;
var
lRect : TRect;
lText : string;
begin
lRect := Rect( 0, 0, htmlStaticText.Width, 0);
lText := htmlStaticText.Text;
aLabel.Caption := htmlStaticText.Text;
aLabel.Font := htmlStaticText.Font;
aLabel.Canvas.Font := htmlStaticText.Font;
aLabel.Canvas.TextRect(
{var} lRect, //will be modified to fit the text dimensions
{var} lText, //not modified, unless you use the "tfModifyingString" flag
[tfCalcRect, tfWordBreak] //flags to say "compute text dimensions with line breaks"
);
ASSERT( lRect.Top = 0 ); //this shouldn't have moved
aLabel.Height := lRect.Bottom;
Result := lRect.Bottom;
end;
The function requires a TLabel component, used exclusively for calculation purposes (you can set the visibility to false). The htmlStaticText component should have AutoSize set to true (in our case AutoSizeType is set to asVertical) and the htmlStaticText.Text should be present when calling the function.
I think THTMLLabel is part of the Jedi Library, itsn't? This problem is weird. I don't know the implementation of this control, but if you are having this problem, then there's may be a very bad implementation of the AutoSize Property.
If you can't inspect and fix the source of this control, try to use the BoundsRect property to get the height:
LabelHeight := THTMLabel(FindComponent('lblSen_0')).BoundsRect.Bottom;
The following code nicely animates adding a new string to the end of a ListBox
procedure TForm6.AddItem(s: string);
var
l : TListBoxItem;
OldHeight : Single;
begin
l := TListBoxItem.Create(Self);
l.Text := s;
OldHeight := l.Height;
l.Height := 0;
l.Parent := ListBox1;
l.Opacity := 0;
l.AnimateFloat('height', OldHeight, 0.5);
l.AnimateFloat('Opacity', 1, 0.5);
end;
The item expands and fades in. However I want to be able to add the string into an arbitrary location in the ListBox - actually at the current ItemIndex.
Does anyone know how to do this?
To work around the fact that ListBox1.InsertObject and ListBox1.Items.Insert don't work you can do the following
procedure TForm1.AddItem(s: string);
var
l : TListBoxItem;
OldHeight : Single;
I: Integer;
index : integer;
begin
l := TListBoxItem.Create(nil);
l.Text := s;
OldHeight := l.Height;
l.Height := 0;
l.Opacity := 0;
l.Index := 0;
l.Parent := ListBox1;
Index := Max(0, ListBox1.ItemIndex);
for I := ListBox1.Count - 1 downto Index + 1 do
begin
ListBox1.Exchange(ListBox1.ItemByIndex(i), ListBox1.ItemByIndex(i-1));
end;
ListBox1.ItemIndex := Index;
l.AnimateFloat('height', OldHeight, 0.5);
l.AnimateFloat('Opacity', 1, 0.5);
end;
but is a bit ridiculous. It (eventually) adds the string in position 0 if there is no item selected, otherwise adds it before the selected item. This solution reminds me too much of Bubble Sort. You will need to add the math unit to your uses clause for the max function to work.
This does indeed seem to be a bug in FireMonkey (check Quality Central #102122), However I suspect a future FireMonkey update will fix this. If anyone can see a better way of doing this....
I've also made a movie about this for those who are interested, which illustrates things more clearly.
This should work, but it does nothing:
l := TListBoxItem.Create(ListBox1);
ListBox1.InsertObject(Max(ListBox1.ItemIndex, 0), l);
If I then call the following, I get an access violation:
ListBox1.Realign;
In fact, even this gives me an AV:
ListBox1.Items.Insert(0, 'hello');
ListBox1.Realign;
But this adds one, of course:
ListBox1.Items.Add('hello');
A bug perhaps?
Instead of
l.Parent := ListBox1;
use
ListBox1.InsertObject(Index, l);
where Index is the insertion position.
(Untested but from reading the sources it should work).
I'm trying to add a column between existing columns in a TListView. Therefor I add the new column at the end and move it by setting it`s index to the designated value. This works, until adding another new column.
What I did:
Add the column at last position (Columns.Add) and add the subitem at the last position (Subitems.Add) too. Afterwards I move the column by setting it's index to the correct position.
This works fine as long as it's just one column that gets added. When adding a second new column, the subitems get screwed up. The new subitem of the first column is moved to the last position, e.g. like this:
0 | 1 | new A | new B | 3
Caption | old sub 1 | old sub 3 | new Sub B | new sub A
I would be very happy if someone could help!
For example, is there maybe a command or message I can send to the ListView so it refreshes or saves it's Column --> Subitem mapping that I could use after adding the first new column and it's subitems so I can handle the second new column the same way as the first.
Or is this just a bug of TListViews column-->subitem handling or TListColumns...?
example code for a vcl forms application (assign the Form1.OnCreate event):
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
listview: TListView;
initButton: TButton;
addColumn: TButton;
editColumn: TEdit;
subItemCount: Integer;
procedure OnInitClick(Sender: TObject);
procedure OnAddClick(Sender: TObject);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
listview := TListView.Create(self);
with listview do
begin
Left := 8;
Top := 8;
Width := self.Width - 30;
Height := self.Height - 100;
Anchors := [akLeft, akTop, akRight, akBottom];
TabOrder := 0;
ViewStyle := vsReport;
Parent := self;
end;
initButton := TButton.Create(self);
with initButton do
begin
left := 8;
top := listview.Top + listview.Height + 20;
Width := 75;
Height := 25;
TabOrder := 1;
Caption := 'init';
OnClick := OnInitClick;
Parent := self;
end;
editColumn := TEdit.Create(self);
with editColumn do
begin
left := initButton.Left + initButton.Width + 30;
top := listview.Top + listview.Height + 20;
Width := 120;
Height := 25;
TabOrder := 2;
Parent := self;
Caption := '';
end;
addColumn := TButton.Create(self);
with addColumn do
begin
left := editColumn.Left + editColumn.Width + 10;
top := listview.Top + listview.Height + 20;
Width := 75;
Height := 25;
TabOrder := 1;
Enabled := true;
Caption := 'add';
OnClick := OnAddClick;
Parent := self;
end;
end;
procedure TForm1.OnInitClick(Sender: TObject);
var col: TListColumn;
i, j: integer;
item: TListItem;
begin
listview.Items.Clear;
listview.Columns.Clear;
// add items
for I := 0 to 2 do
begin
col := ListView.Columns.Add;
col.Caption := 'column ' + IntToStr(i);
col.Width := 80;
end;
// add columns
for I := 0 to 3 do
begin
item := ListView.Items.Add;
item.Caption := 'ItemCaption';
// add subitems for each column
for j := 0 to 1 do
begin
item.SubItems.Add('subitem ' + IntToStr(j+1));
end;
end;
subItemCount := 5;
end;
procedure TForm1.OnAddClick(Sender: TObject);
var number: integer;
col: TListColumn;
i: Integer;
ascii: char;
begin
listview.Columns.BeginUpdate;
number := StrToInt(editColumn.Text);
ascii := Chr(65 + number);
// create the new column
col := TListColumn(ListView.Columns.add());
col.Width := 80;
col.Caption := ascii;
// add the new subitems
for I := 0 to ListView.Items.Count-1 do
begin
ListView.Items[i].SubItems.Add('subitem ' + ascii);
end;
// move it to the designated position
col.Index := number;
listview.Columns.EndUpdate;
Inc(subItemCount);
end;
end.
Thank you!
Edit: The suggested fix from Sertac Akyuz works fine, though I can't use it because changing the Delphi sourcecode is no solution for my project. Bug is reported.
Edit: Removed the second question that was unintended included in the first post and opened new question (See linked question and Question-revision).
Update: The reported bug is now closed as fixed as of Delphi XE2 Update 4.
Call the UpdateItems method after you've arranged the columns. E.g.:
..
col.Index := number;
listview.UpdateItems(0, MAXINT);
..
Update:
In my tests, I still seem to need the above call in some occasion. But the real problem is that "there is a bug in the Delphi list view control".
Duplicating the problem with a simple project:
Place a TListView control on a VCL form, set its ViewStyle to 'vsReport' and set FullDrag to 'true'.
Put the below code to the OnCreate handler of the form:
ListView1.Columns.Add.Caption := 'col 1';
ListView1.Columns.Add.Caption := 'col 2';
ListView1.Columns.Add.Caption := 'col 3';
ListView1.AddItem('cell 1', nil);
ListView1.Items[0].SubItems.Add('cell 2');
ListView1.Items[0].SubItems.Add('cell 3');
Place a TButton on the form, and put the below code to its OnClick handler:
ListView1.Columns.Add.Caption := 'col 4';
Run the project and drag the column header of 'col 3' to in-between 'col 1' and 'col 2'. The below picture is what you'll see at this moment (everything is fine):
Click the button to add a new column, now the list view becomes:
Notice that 'cell 2' has reclaimed its original position.
Bug:
The columns of a TListView (TListColumn) holds its ordering information in its FOrderTag field. Whenever you change the order of a column (either by setting the Index property or by dragging the header), this FOrderTag gets updated accordingly.
Now, when you add a column to the TListColumns collection, the collection first adds the new TListColumn and then calls the UpdateCols method. The below is the code of the UpdateCols method of TListColumns in D2007 VCL:
procedure TListColumns.UpdateCols;
var
I: Integer;
LVColumn: TLVColumn;
begin
if not Owner.HandleAllocated then Exit;
BeginUpdate;
try
for I := Count - 1 downto 0 do
ListView_DeleteColumn(Owner.Handle, I);
for I := 0 to Count - 1 do
begin
with LVColumn do
begin
mask := LVCF_FMT or LVCF_WIDTH;
fmt := LVCFMT_LEFT;
cx := Items[I].FWidth;
end;
ListView_InsertColumn(Owner.Handle, I, LVColumn);
Items[I].FOrderTag := I;
end;
Owner.UpdateColumns;
finally
EndUpdate;
end;
end;
The above code removes all columns from the underlying API list-view control and then inserts them anew. Notice how the code assigns each inserted column's FOrderTag the index counter:
Items[I].FOrderTag := I;
This is the order of the columns from left to right at that point in time. If the method is called whenever the columns are ordered any different than at creation time, then that ordering is lost. And since items do not change their positions accordingly, it all gets mixed up.
Fix:
The below modification on the method seemed to work for as little as I tested, you need to carry out more tests (evidently this fix does not cover all possible cases, see 'torno's comments below for details):
procedure TListColumns.UpdateCols;
var
I: Integer;
LVColumn: TLVColumn;
ColumnOrder: array of Integer;
begin
if not Owner.HandleAllocated then Exit;
BeginUpdate;
try
SetLength(ColumnOrder, Count);
for I := Count - 1 downto 0 do begin
ColumnOrder[I] := Items[I].FOrderTag;
ListView_DeleteColumn(Owner.Handle, I);
end;
for I := 0 to Count - 1 do
begin
with LVColumn do
begin
mask := LVCF_FMT or LVCF_WIDTH;
fmt := LVCFMT_LEFT;
cx := Items[I].FWidth;
end;
ListView_InsertColumn(Owner.Handle, I, LVColumn);
end;
ListView_SetColumnOrderArray(Owner.Handle, Count, PInteger(ColumnOrder));
Owner.UpdateColumns;
finally
EndUpdate;
end;
end;
If you are not using packages you can put a modified copy of 'comctrls.pas' to your project folder. Otherwise you might pursue run-time code patching, or file a bug report and wait for a fix.
i have create 5 dynamic buttons in 1 row and store the button info (caption, isActive, etc) in registry. It shows the button in form when the IsActive = TRUE from registry. The result is as below (each button indicate by a * symbol):
*****
When i set the IsActive = FALSE for button2 and button4, the button being remove and left the space as below:
* ** *
Any idea to solve this problem? The source code is as below:
procedure TfrmPOS.CreateDynamicBtn;
var
Reg : TRegIniFile;
lstKey : TStringList;
sKeyName : String;
bActive : boolean;
btn1 : TBitBtn;
i, k, iIcon : integer;
begin
lstKey := TStringList.Create;
Reg := TRegIniFile.Create;
try
//clear bitbtn
if ScrollBox2.ControlCount > 0 then begin
for k := ScrollBox2.ControlCount - 1 downto 0 do begin
with ScrollBox2.Controls[k] AS TBitBtn do begin
Free;
end;
end;
end;
sKeyName := Sysmem.RegKeyRoot+'\POSConfig\ItemSetting\';
Reg := TRegIniFile.Create(sKeyName);
Reg.GetKeyNames(lstKey); //button1, button2,...
for i := 0 to lstKey.Count - 1 do begin
Reg.OpenKeyReadOnly(sKeyName);
bActive := Reg.ReadBool(lstKey.Strings[i], 'IsActive', TRUE);
if bActive = TRUE then begin
//create dynamic bitbtn
btn1 := TBitBtn.Create(self);
btn1.Parent := ScrollBox2;
btn1.Height := 82;
btn1.Width := 82;
btn1.Left := ((i mod 5) * btn1.Width);
btn1.Top := (Trunc((i div 5)) * btn1.Height);
btn1.Caption := Reg.ReadString(lstKey.Strings[i], 'Caption', '');
iIcon := Reg.ReadInteger(lstKey.Strings[i], 'IconImage', 0);
imglstIcon.GetBitmap(iIcon, btn1.Glyph);
btn1.Layout := blGlyphTop;
btn1.Name := lstKey.Strings[i];
btn1.OnClick := OnButtonClick;
end;
Reg.CloseKey;
end;
finally
lstKey.Free;
Reg.Free;
end;
end;
I suspect you wonder why the space for the second button is still there, instead of the third button filling that area.
It's because you're setting the Left properties for the buttons as though all the buttons were there:
btn1.Left := ((i mod 5) * btn1.Width);
When i = 1, you skip over it because that button is invisible. But when i = 3, you calculate its position the same as you would have if button 2 had been visible. Keep a visible-button counter separate from your list iterator, and use it to position your buttons:
BtnIndex := 0;
Reg.OpenKeyReadOnly(sKeyName);
for i := 0 to lstKey.Count - 1 do begin
bActive := Reg.ReadBool(lstKey.Strings[i], 'IsActive', TRUE);
if bActive then begin
//create dynamic bitbtn
btn1 := TBitBtn.Create(self);
btn1.Parent := ScrollBox2;
btn1.SetBounds(BtnIndex mod 5 * 82, BtnIndex div 5 * 82, 82, 82);
Inc(BtnIndex);
There are better ways to do what you want. If you have a sufficiently recent version of Delphi, use a TFlowPanel or TGridPanel. They will arrange your buttons next to each other for you automatically. If your Delphi version didn't come with that control, then try a TToolBar instead and fill it with TToolButton controls.
Your question actually had nothing to do with the registry, but you can make better use of the registry anyway. There's no need to keep re-opening the same key every time. The value of sKeyName doesn't change inside the loop, so open the key once before you enter the loop (as shown above) and then just leave it open for as long as you need it.