Newsgroup: it.comp.lang.delphi
Titolo: Frame con una row di edit

Ho cominciato a lavorare con le frames e ho creato un progetto di prova dove 
ho utilizzato alcuni esempi suggeriti da Paolo Arosio e Alberto Salvati. 
Tutto funziona, nel senso che in un pannello ho una riga di edit (una frame) 
e due bottoni per aggiungere o rimuovere righe addizionali. L'aggiunta 
funziona, la rimozione anche, con una possibile soluzione, ma non con una 
seconda che a prima vista mi pareva meglio.. Poichè sospetto che dietro il 
mancato funzionamento di questa seconda ci sia la mancata comprensione di 
come funzionano le classi, chiedo aiuto per arrivare a capire dove sbaglio.
Nella proc che non funziona, tento di rimuovere le righe sul pannello 
individuandole attraverso la TList (dove le carico in fase di aggiunta).  La 
rimozione funziona, se tutte le righe aggiunte vengono rimosse in un botto 
solo. Ma se tento di modificarla ( con un semplice exit) per rimuovere le 
righe una ad una, allora succede che la prima riga (metti Row3) è 
correttamente rimossa. Se poi ( con un secondo clic del bottone ) si tenta 
di rimuovere la Row2, la proc trova prima nella TList la Row3 e il test 
inteso a bypassarla ( se nil  ) fallisce e quindi il successivo FreeAndNil 
dà il messaggio "Invalid pointer operation". Perchè ?
 
Ecco di seguito il codice:
 
procedure TForm1.BtnAddRowClick(Sender: TObject);
{ Ogni volta che il bottone è premuto, entra in questa routine dove
  si crea un nuovo oggetto newRow di tipo TFrameEdit dandogli un nome che 
inizia
  per 'Row' e con un suffisso numerico che si incremento ad ogni nuovo 
ingresso}
var
  newRow: TFrameEdit;
begin
  newRow := TFrameEdit.Create(Self);
  inc(numRows);
  // posiziona il nuovo oggetto sotto l'ultimo create e referenziato da 
lastRow
  newRow.Top := lastRow.Top + lastRow.Height;
  newRow.Left := lastRow.Left;
  newRow.Name := 'Row' + inttostr(numRows);
  newRow.Parent := Self;
  lastRow := newRow;
 
  frmList.Add(newRow); // Aggiungi alla TList
end;
 
QUESTA PROC NON FUNZIONA ....
procedure TForm1.BtnClearRowClick(Sender: TObject);
var
   i,k: integer;
begin
   for i := frmList.Count - 1 downto 0 do
   begin
      aFrame := TFrameEdit(frmList.Items[i]);
      if aFrame <> nil then
         FreeAndNil(aFrame);   // "Invalid pointer operation"   !!!!!
      exit; // rimuovere se si vogliono rimuovere Tutte le righe in un solo 
botto
   end;
end;
 
  QUESTA INVECE FUNZIONA ...
procedure TForm1.BtnClearRowClick(Sender: TObject);
var
   k: integer;
begin
  for k := ComponentCount-1 downto 0 do
  begin
  if (Components[k] is TFrameEdit) then
    if Copy(Components[k].Name, 1, 3) = 'Row' then
    begin
      Components[k].Free;
      break;
    end;
  end; 
 

Ma hai letto il codice esemplificativo che avevio postato? Mi sa di
no...
Mi spiego.
Tu crei il frame dando giustamente come owner il tuo form e poi lo
metti in una tlist.
Ora, il tuo frame e' referenziato in *due diverse collecition*:
 
1) form.controls (ricorda che TFrame eredita da TControl oltre che da
TComponent...)
2) la tua lista di frame
 
Quindi, se tu fai la free dell'elemento i-esimo della tlist, nella
collection form.controls resta un riferimento ad un oggetto che non
esiste piu.
Poi, chissa la vcl che fa e smanettando con form.controls e trovando
un riferimento non valido manda tutto ai festini del nano di
Arcore....
 
Quindi, come gia scritto nel post precedente, la cosa andrebbe fatta
cosi:
 
 procedure TForm1.BtnClearRowClick(Sender: TObject);
 var
    i,k: integer;
 begin
    for i := frmList.Count - 1 downto 0 do
    begin
       aFrame := TFrameEdit(frmList.Items[i]);
       FORM.REMOVECONTROL(AFRAME);
       FreeAndNil(aFrame);
    END;
END;
 
Prova e vedi se esplode.
 
A.

 individuandole attraverso la TList (dove le carico in fase di aggiunta).
 
Già il fatto che controlli se il valore ottenuto dalla TList sia diverso da *nil* fa venire qualche dubbio: quando il controllo viene rimosso, dovrebbe essere tolto anche dalla lista, quindi non dovresti avere alcun puntatore non impostato all'interno di questa.
 
Una domanda: la procedura che hai indicato è l'unica dove fai la rimozione dei Frame o ve ne sono altre?
 
Secondo me, è più conveniente che tu posti il codice completo per poterlo eventualmente testare nella sua interezza.
 
Ciao!
  Marco.
 
-- 
MARCO BREVEGLIERI
#Home: 
http://www.marco.breveglieri.name
 
#Blog: 
http://www.compilaquindiva.net
 

 ha scritto nel messaggio 
news:3d46538c-f0a4-4a1d...@o9g2000vbc.googlegroups.com...

L'ho letto il tuo codice. E infatti sono arrivato fino  questo punto, prima 
non ne sapevo niente.. Comunque, per confermare se ho capito o no, quando 
aggiungo una frame cliccando sull'apposito bottone, il programma esegue due 
istruzioni princiupali:
 
  newRow := TFrameEdit.Create(Self);
  frmList.Add(newRow);
 
A questo punto io ho creato UN SOLO OGGETTO, referenziato due volte (da Form 
e da frmList), o aggiungendolo nella TList ho creato una ULTERIORE ISTANZA ?
 
Nella proc che intende rimuovere l'ultima frame aggiunta al pannello ricorro 
alla TList per sapere quale è  quest'ultima frame:
Avevo tentato di fare :   FreeAndNil(TFrameEdit(frmList.Items[i]));    ma mi 
dà errore, quindi ho fatto:
    aFrame := TFrameEdit(frmList.Items[i]);
    FreeAndNil(aFrame);
La prima istruzione crea forse un unteriore pointer alla frame ? Ma in che 
altro modo avrei potuto fare ?
 
Comunque ho provato ad aggiungere l'istruzione : 
FORM1.REMOVECONTROL(AFRAME);  ma mi dà sempre invalid pointer operation. 
Allora ho tentato di cancellare l'elemento della TList con
    frmList.Delete(i);
e questo sembra funzionare!
 
Non che con questo ho risolto tutti i problemi.. A parte le domande sopra, 
adesso come faccio a capire quali edit sono stati usati dall'utente e che 
valori ci sono ?
 
PS: Ho dimenticato di ringraziare ? GRAZIE DAVVERO ! 
 

 individuandole attraverso la TList (dove le carico in fase di aggiunta).
 
Gi� il fatto che controlli se il valore ottenuto dalla TList sia diverso da 
*nil* fa venire qualche dubbio: quando il controllo viene rimosso, dovrebbe 
essere tolto anche dalla lista, quindi non dovresti avere alcun puntatore 
non impostato all'interno di questa.
 
Una domanda: la procedura che hai indicato � l'unica dove fai la rimozione 
dei Frame o ve ne sono altre?
 
Secondo me, � pi� conveniente che tu posti il codice completo per poterlo 
eventualmente testare nella sua interezza.
 
Ciao!
  Marco.
 
-- 
MARCO BREVEGLIERI
#Home: 
http://www.marco.breveglieri.name
 
#Blog: 
http://www.compilaquindiva.net
 
 
  ......................
  ......................
  TfrmList = class(TList)
  end;
 
var
  Form1: TForm1;
  lastRow: TFrameEdit;
  numRows: integer;
  frmList: TfrmList;
  newRow: TFrameEdit;
  aFrame: TFrameEdit;
 
implementation
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  //
  frmList:= TfrmList.Create;
  //
  firstRow.SpeedButton1.Enabled := false;
  firstRow.SpeedButton1.OnClick := nil;
  initIt;
end;
 
procedure TForm1.initIt;
begin
  lastRow := firstRow;
  numRows := 1;
end;
 
procedure TForm1.btnAddRowClick(Sender: TObject);
var newRow: TfrmEdit;
begin
  newRow := TfrmEdit.Create(Self);
  inc(numRows);
  newRow.Top := lastRow.Top + lastRow.Height;
  newRow.Left := lastRow.Left;
  newRow.Name := 'Row' + inttostr(numRows);
  newRow.Parent := Self;
  lastRow := newRow;
  //
  frmList.Add(newRow);
  //
end;
 
procedure TForm1.btnClearRowsClick(Sender: TObject);
var i: integer;
begin
begin
   for i := frmList.Count - 1 downto 0 do
   begin
      aFrame := TFrameEdit(frmList.Items[i]);
      if aFrame <> nil then
         FreeAndNil(aFrame);
      break;
   end;
  initIt;
end;
 
 
Praticamente quello sopra � tutto il codice. Ho letto le osservazioni di 
Alberto Salvati, a cui ho le chiesto ulteriori delucidazioni seguenti:
Per confermare se ho capito o no, quando aggiungo una frame cliccando 
sull'apposito bottone, il programma esegue due istruzioni principali:
 
  newRow := TFrameEdit.Create(Self);
  frmList.Add(newRow);
 
A questo punto io ho creato UN SOLO OGGETTO, referenziato due volte (da Form 
e da frmList), o aggiungendolo nella TList ho creato una ULTERIORE ISTANZA ?
 
Nella proc che intende rimuovere l'ultima frame aggiunta al pannello ricorro 
alla TList per sapere quale �  quest'ultima frame:
Avevo tentato di fare :   FreeAndNil(TFrameEdit(frmList.Items[i]));    ma mi 
d� errore, quindi ho fatto:
    aFrame := TFrameEdit(frmList.Items[i]);
    FreeAndNil(aFrame);
La prima istruzione crea forse un unteriore pointer alla frame ? Ma in che 
altro modo avrei potuto fare ?
 
Comunque ho provato ad aggiungere l'istruzione : 
FORM1.REMOVECONTROL(AFRAME);  ma mi d� sempre invalid pointer operation. 


 e questo sembra funzionare!
 
 
 
I "penso", "immagino", "sembra", lasciamoli agli artisti. I "credo" a
chi va in chiesa.
Che kaz vuol dire "sembra"??
 
A.

 e da frmList), o aggiungendolo nella TList ho creato una ULTERIORE ISTANZA?
 
Questa è una domanda che lascia un pochino perplessi: se non sai come funzionano i riferimenti a oggetti in Delphi, devi partire da quello, dall'ABC, prima di cimentarti con architetture più complesse.
 
Sul resto capirai anche tu che è difficoltoso iniziare a parlarne se ci sono problemi sui fondamentali. :|
 
Ciao,


Ok, ho preso un bel pò di legnate, me persevero e provo a rispondermi da 
solo:
  newRow := TFrameEdit.Create(Self);  => cre un oggetto newRow, cioè 
un'istanza della classe TFrameEdit
  frmList.Add(newRow);                        => aggiungo alla TList un 
pointer allo stesso oggetto
Creo alcune istanze dell'oggetto, che convivono in memoria, e un pointer è 
aggiunto per ciascuna di esse nella TList.
L'oggetto è una frame che viene visualizzata su un pannello come una riga di 
edit. Ogni nuovo oggetto, una nuova riga.
 
Adesso voglio cancellare l'ultima riga, poi successivamente la penultima .. 
(Usavo un loop della TList, perchè inizialmente avevo pensato di cancellarle 
tutte).
L'ultima riga viene cancellata con :
    aFrame := TFrameEdit(frmList.Items[i]);
    FreeAndNil(aFrame);
 
Per cancellare le successive, poichè la TList continua a mantenere i 
pointers a tutte le righe, dovevo bypassare quelle righe già cancellate 
(ecco cosa tentava di fare il test if aFrame <> nil).
 
Quando ho affermato che la proc di cancellazione 'sembra' funzionare con 
l'istruzione che elimina l'elemento dalla TList ( frmList.Delete(i); ) 
volevo dire che la cancellazione delle righe FUNZIONA, ma non so se lascio 
in memoria delle porcherie...
 
   for i := frmList.Count - 1 downto 0 do
   begin
      aFrame := TFrameEdit(frmList.Items[i]);
      FreeAndNil(aFrame);
      frmList.Delete(i);
      break;   // questa istr. la tolgo se devo canc. tutto
   end;
 
 
 

    end;
 
Che vuol dire "la tolgo"? Devi modificare i sorgenti, ricompilare
etc??
 
Fermo restando che io stesso devo chiarirmi la storia di
RemoveCocontrol etc (vedi il post di Arosio...), io farei cosi:
 
 
procedure TMyForm.RemoveAllRows;
var
  i: integer;
begin
  for i := Pred(FrameList.Count) downto 0 to
  begin
    RemoveRow(i);
  end;
end;
 
 
procedure TMyForm.RemoveRow(const ARowIndex: integer);
var
  TempObj: TObject;
  FrameForDelete: TFrameEdit;
begin
 
     if (ARowIndex > -1) and (ARowIndex < FrameList.count)  then
     begin
           TempObjt := FFrameList[ARowIndex];
           if (Assigned(TempObj)) and (TempObj is TFrameEdit) then
           begin
             FrameForDelete := TFrameEdit(TempObj);
             Container.RemoveControl(FrameForDelete);
             FreeAndNil(FrameForDelete);
           end
           else
           begin
             // log error: object is nil or is not valid for typecast
to TFrameEdit
           end;
 
 
     end
     else
     begin
       // log error: index out of bound
     end;
     FrameList[i]
 
end;
 
 
procedure TMyForm.btnRemoveCurrentRowClick(sender: TObject);
begin
    removeRow(CurrentRowIndex);
end;
 
Ho scritto "container".
Li va il nome del form o quello della scrollbox di cui parlavo n post
fa.
 
 
A.
 
 

Grazie. Mi č chiara la teoria, meno l'implementazione (ho studito, ma non 
basta). Ho provato e funziona tutto. Ho apportato le modifiche segnate con 
////
 
procedure TMyForm.RemoveAllRows;
var
  i: integer;
begin
  for i := Pred(frmList.Count) downto 0 to
  begin
    RemoveRow(i);
    frmList.Delete(frmList.Count-1);  //// aggiunta per evitare che, dopo la 
rimozione, un altro clic tenti di rimuovere quello che non c'č piů
  end;
end;
 
procedure TMyForm.RemoveRow(const ARowIndex: integer);
var
  TempObj: TObject;
  FrameForDelete: TFrameEdit;
begin
     if (ARowIndex > -1) and (ARowIndex < frmList.count)  then
     begin
           TempObjt := frmList[ARowIndex];
           if (Assigned(TempObj)) and (TempObj is TFrameEdit) then
           begin
             FrameForDelete := TFrameEdit(TempObj);
             Container.RemoveControl(FrameForDelete);
             FreeAndNil(FrameForDelete);
           end
           else
           begin
             // log error: object is nil or is not valid for typecast to 
TFrameEdit
           end;
     end
     else
     begin
       // log error: index out of bound
     end;
     FrameList[i]  //// ???? tolta
end;
 
procedure TMyForm.btnRemoveCurrentRowClick(sender: TObject);
begin
    removeRow(CurrentRowIndex);  //// sostituita da segg
   //// ho usato frmList.Count-1 come CurrentRowIndex e pertanto fatto il 
delete dalla TList
   if frmList.Count > 0 then
   begin
      removeRow(frmList.Count-1);
      frmList.Delete(frmList.Count-1);
   end;
end;
 

 valori ci sono ?
 
Ho modificato un paio di cose, così forse è più chiaro sia il
meccanismo di aggiunta / rimozione, sia il modo con cui puoi
accorgerti di cosa fa l'utente.
 
Posto tutto, giusto per chiarezza (scusate per la lunghezza del
messaggio)
 
****** Codice del frame *******
unit ufrmEdit;
 
interface
 
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs,
  Buttons, StdCtrls;
 
type
  TfrmEdit = class(TFrame)
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    SpeedButton1: TSpeedButton;
    procedure SpeedButton1Click(Sender: TObject);
  private     { Private declarations }
    fDeleting: TNotifyEvent;
    function getModified: boolean;
  public      { Public declarations }
    property onDelete: TNotifyEvent read fDeleting write fDeleting;
    property Modified: boolean read getModified;
  end;
 
implementation
 
{$R *.dfm}
 
function TfrmEdit.getModified: boolean;
begin
  Result := Edit1.Modified or Edit2.Modified or Edit3.Modified;
end;
 
procedure TfrmEdit.SpeedButton1Click(Sender: TObject);
begin
  if Assigned(fDeleting) then
    fDeleting(Self);
  Free;
end;
 
end.
 
 
**** form contenente una scrollbox, con un frame a design time, un
button per aggiungere righe e uno per cancellarle tutte *******
Nota che non ho variabli globali (se non la form stessa). Tutto ciò
che serve è o locale, o è un membro della form. In questa versione,
numRow e lastRow non servono più: prova ad eliminarle in modo che
tutto funzioni (in raltà, così com'è, si "rompe" se elimini l'ultima
riga e poi tenti di riaggiungerne), oltre a gestire la ridisposizione
dei frame a seguito della rimozione di una riga.
 
unit ufrmMain;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms,
  Dialogs, StdCtrls, ufrmEdit;
 
type
  TForm1 = class(TForm)
    btnAddRow: TButton;
    btnClearRows: TButton;
    ScrollBox1: TScrollBox;
    firstRow: TfrmEdit;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btnAddRowClick(Sender: TObject);
    procedure btnClearRowsClick(Sender: TObject);
  private  { Private declarations }
    procedure initIt;
    procedure handleRowDeletion(Sender: TObject);
  public  { Public declarations }
    lastRow: TfrmEdit;
    numRows: integer;
    rowList: TList;
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.dfm}
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  firstRow.SpeedButton1.OnClick := nil;
  firstRow.SpeedButton1.Enabled := false;
  rowList := TList.Create;
  initIt;
end;
 
procedure TForm1.FormDestroy(Sender: TObject);
begin
  // No need to free frames: they are owned by the form
  rowList.Free;
end;
 
procedure TForm1.initIt;
begin
  rowList.Clear;
  lastRow := firstRow;
  numRows := 1;
end;
 
procedure TForm1.btnAddRowClick(Sender: TObject);
var newRow: TfrmEdit;
begin
  newRow := TfrmEdit.Create(Self);
  inc(numRows);
  rowList.Add(newRow);
  newRow.onDelete := handleRowDeletion; // <= gestisce la rimozione di
una riga
  newRow.Top := lastRow.Top + lastRow.Height;
  newRow.Left := lastRow.Left;
  newRow.Name := 'Row' + inttostr(numRows);
  newRow.Parent := lastRow.Parent;
  lastRow := newRow;
  ShowMessage('Now I''ve got ' + inttostr(rowList.Count) + ' frames');
end;
 
procedure TForm1.btnClearRowsClick(Sender: TObject);
var i: integer;
begin
  for i := ComponentCount-1 downto 0 do begin
    if Copy(Components[i].Name, 1, 3) = 'Row' then begin
      Components[i].Free;
    end;
  end;
  initIt;
end;
 
procedure TForm1.handleRowDeletion(Sender: TObject);
var i: integer;
begin
  ShowMessage(TfrmEdit(Sender).Name + ' is being deleted');
  for i := rowList.Count-1 downto 0 do begin
    if rowList.Items[i] = Sender then begin
      rowList.Delete(i);
      break;
    end;
  end;
  ShowMessage('Now I''ve got ' + inttostr(rowList.Count) + ' frames');
  // todo: recompact list visually
  // Attenzione: qui il frame ancora esiste!
end;
 
end.
 
 
 

Non so come ringraziare chi ha contribuito a questa discussione. Grazie 
anche di quest'ultimo apporto. Penso di essere arrivato ad una soluzione che 
funziona, ma esperimenterò qualsiasi suggerimento, perchè ci impiego pochi 
minuti e imparo come se avessi letto per ore.
Non ho ancora fatto mente locale, ma per ricompattare dopo una 
cancellazione, non si potrebbe ricostruire le righe da capo partendo dai 
riferimenti rimasti nella TList ?
Secondo osservazione: la proc per cancellare tutte le righe cancella tutti i 
componenti il cui nome inizia per 'row' e funziona. Ma che obiezioni hai 
alla procedura suggerita da Salvati ?