Mehrere Frames aktualisieren (FindComponent)

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
charlytango
Beiträge: 671
Registriert: Sa 12. Sep 2015, 12:10
OS, Lazarus, FPC: Laz 2.2.4
CPU-Target: Win 32Bit, 64bit
Wohnort: Wien

Mehrere Frames aktualisieren (FindComponent)

Beitrag von charlytango »

Hi,

In ein PageControl wurden zur Laufzeit auf mehreren Tabsheets je ein Frame "eingeklebt".
Tabsheets und Frames werden zur Laufzeit (teilweise vom User) erzeugt.
Funktioniert alles wie erwartet.

Auf einigen dieser Frames sind TDBGrids (mit je eigener Datasource).
Nun sollen Datensätze von einem Frame zum anderen "kopiert" werden.
In Wahrheit werden ausgewählte Datensätze nur in eine DB-Tabelle kopiert und sollen vom anderen Frame aus der DB eingelesen werden -- passt, solange ich zum betroffenen Frame gehe und die Aktualisierung per Button starte.

Allerdings soll nun die Anzeige des Frames in das "kopiert" wird automatisch gleich nach dem Kopiervorgang aktualisiert werden.

Nachdem ich den Namen des Frames weiß kann ich das Frame suchen und die Aktualisierung starten. Dachte ich zumindest

Code: Alles auswählen

procedure TFramSelektStandard.RefreshTarget(sComponentName: string);
var
  c:TComponent;
begin
  c:=FindComponent(sComponentName);
  //gefundene Komponente auf den Frametyp casten und die Abfrage aktualisieren
  TFramSelektStandard(c).RunQuery; 
end;
die Aktualisierung sieht exemplarisch ungefär so aus:

Code: Alles auswählen

procedure TFramSelektStandard.RunQuery;
begin
  ZQuery.DisableControls;

  if ZQuery.Active then
    ZQuery.Close;
	
  try
    //SQL zur Abfrage erstellen 
    ZQuery.SQL.Text := FoMyQBE.GenerateSQL;
  finally
    freeandnil(FoMyQBE);
  end;
  
  ZQuery.Open;
  ZQuery.EnableControls;
end;   


Lässt sich kompilieren, läuft.
Zur Laufzeit scheint das richtige Frame gefunden zu werden, allerdings bekomme ich bei der Zeile ZQuery.DisableControls; eine ACCESS VIOLATION.
kommentiere ich die aus kracht es eben eine Zeile später.

Nun verstehe ich nicht warum eine gefundene Komponente, die ja existiert Probleme macht wenn ich die DB-Anbindung aktualisieren will. Zwar ist das Zielframe in diesem Moment nicht sichtbar aber zumindest ein ZQuery.Active sollte doch klappen?

Irgendwelche grundlegenden Ideen die euch ins Auge springen bevor ich eine Demo bauen muss? ;)

charlytango
Beiträge: 671
Registriert: Sa 12. Sep 2015, 12:10
OS, Lazarus, FPC: Laz 2.2.4
CPU-Target: Win 32Bit, 64bit
Wohnort: Wien

Re: Mehrere Frames aktualisieren (FindComponent)

Beitrag von charlytango »

Nun hab ich einiges ausprobiert, komme aber trotzdem auf keinen grünen Zweig.
Die Demo hab ich in das Framebespiel aus einem der letzten Threads eingebaut.

Auf dem Hauptformular einfach den Button "FindComponent" drücken und die SIGSEV genießen ;-)

Der Button sucht ein bestimmtes Frame und startet dessen Prozedur "comptest".

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var
  c:TComponent;
begin
  c:=PageControl1.FindComponent('Frame2');
 // if assigned(c) then     {Als Überprüfung der erfolgreichen Suche scheint das nicht zu taugen}
     TMyFrame(c).comptest;
end; 
Als Nebenfrage wüsste ich gern wie man den erfolgreichen Suchvorgang abfragen kann, assigned() scheint dafür nicht zu taugen ????

Code: Alles auswählen

procedure TMyFrame.comptest;
begin
  SQLQuery1.DisableControls;  //  <<-- hier kracht es.
  //Hier wäre die eigentliche SQL-Abfragezu finden
  showmessage('Heureka');
  SQLQuery1.EnableControls;
end;
Ohne Zugriff auf SQLQuery1 (also nur das showmessage) klappt es ohne Probleme.
Dateianhänge
project1.zip
(141.07 KiB) 30-mal heruntergeladen

wp_xyz
Beiträge: 4501
Registriert: Fr 8. Apr 2011, 09:01

Re: Mehrere Frames aktualisieren (FindComponent)

Beitrag von wp_xyz »

Das Vorgehen bei mir ist in solchen Fällen immer: Breakpoint an den Anfang des auslösenden Event-Handler setzen, hier auf Button1Click, 1. Zeile "c := ...". Programm starten und das Event auslösen, also auf den Button klicken. Wenn der Debugger hält, das Programm schrittweise durchgehen. Hier hält der Debugger schon auf "c := ...". Mit der Maus über die variable "c" fahren - das Popup-Fenster zeigt, dass c nil ist. Da c durch ein FindComponent bestimmt wird, liegt nahe, dass PageControl1.FindComponent('Frame2') die Komponente nicht gefunden hat...

Warum ist das so? Weil Control.FindComponent die Komponenten sucht, bei denen Control als Owner eingetragen ist. Wenn du eine Komponente aufs Formular klickst, wird das Formular immer zum Owner (ok - vielleicht nicht immer...). Wenn du eine Komponente zur Laufzeit erzeugst, kommt es darauf an, was im Create-Aufruf angegeben ist. Du schreibst: "MyFrame2 := TMyFrame.Create(Panel2)" , d.h. der Owner von MyFrame2 ist das Panel2, aber nicht das PageControl. Daher durchsucht FindComponent die Komponentenliste von Panel2, nicht die des PageControl.

Da Panel2 zu einem TabSheet gehört, wird es ganz schön kompliziert, den Frame zu finden... In der allgemeinsten Form müsstest du dir eine Methode für TForm schreiben, die rekursiv alle Components einzeln durchläuft und jeweils FindComponent aufruft, und wenn nichts gefunden ist, rekursiv mit den Komponenten weitermacht, die die aktuell zu prüfende Komponente als Owner haben.

Aber es geht auch einfacher: Erzeuge die Frames einfach mit "self" (also dem Formular) als Owner - dann werden die Frames in der Components-Liste des Formulars eingetragen, und Formular.FindComponent kann den Frame finden.

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var
  c: TComponent;
begin
  c := FindComponent('Frame2');
  if (c is TMyFrame) then
     TMyFrame(c).comptest
  else
    ShowMessage('Oops - falsche Komponente erwischt. Irgendwas ist faul...');
end;
Die korrekte Abfrage, ob die gefundene Komponente ein TMyFrame ist, machst du mit Hilfe von "is": "if (c is TMyFrame)". Dies prüft auch auf nil (also den Fall "nicht gefunden").

charlytango
Beiträge: 671
Registriert: Sa 12. Sep 2015, 12:10
OS, Lazarus, FPC: Laz 2.2.4
CPU-Target: Win 32Bit, 64bit
Wohnort: Wien

Re: Mehrere Frames aktualisieren (FindComponent)

Beitrag von charlytango »

OK, danke -- das habe ich mal verstanden (hoffentlich).
Scheint nicht ganz so einfach zu sein...
Real ist die Struktur so:

TForm
TPageControl
TTabSheet
TFrame

Auf einem TForm klebt ein TPageControl
Auf einem einzelnen TTabSheet des TPageControl ist ein TFrame eingehängt.
Es gibt unterschiedliche Typen von TFrames die in einer nicht vorhersehbaren Menge von TTabsheets eingehängt sind.
Die TFrames könnten einen gemeinsamen Vorfahren haben.
Nur wenige TFrame-Typen sind refreshbar, das Frame weiß aber ob es refreshbar ist.
Controls um diesen Refresh auszulösen befinden sich auf den TFrames. (in meinem Fall eine Klick auf ein Popup-Menü Item )

Im Moment des Clicks kenne ich den Namen des Frames das zu refreshen ist.

Demnach brauche ich irgend eine Art Kommunikation entweder zwischen den Frames direkt
oder eine Kommunikation des Frames mit dem TForm das seinerseits das richtige Frame sucht und den Refresh auslöst. Denke da vermutlich viel zu kompliziert grrrr
Puhhh :(

Im Moment probiere ich den Vorschlag aus der Demo aber scheinbar noch ein dickes Brett vorm Kopf.

wp_xyz
Beiträge: 4501
Registriert: Fr 8. Apr 2011, 09:01

Re: Mehrere Frames aktualisieren (FindComponent)

Beitrag von wp_xyz »

charlytango hat geschrieben:
Mo 3. Okt 2022, 09:50
Demnach brauche ich irgend eine Art Kommunikation entweder zwischen den Frames direkt
oder eine Kommunikation des Frames mit dem TForm das seinerseits das richtige Frame sucht und den Refresh auslöst. Denke da vermutlich viel zu kompliziert grrrr
Puhhh :(
Zum Finden der Frames würde ich den Zeiger auf den Frame einfach im Tag der TabSheets abspeichern. Dann kannst du im PageControl.OnChange aus dem pageControl.ActivePage.Tag einfach den neuen aktiven Frame auslesen.

Zum Aktivieren von Refresh-Aktionen im Frame, die nur dieser im Detail kennt, können Events hilfreich sein. Gib jedem Frame ein Event OnRefresh, und implementiere den Handler im Formular:

Code: Alles auswählen

type
  TMyFrame = class(TFrame)
  private
    FOnRefresh: TNotifyEvent;
    procedure RefreshFrame;
  ...
  public
    property OnRefresh: TNotifyEvent read FOnRefresh write FOnRefresh;
    ...
  end;
  
   TForm1 = class(TForm)
   private
     procedure CreateFrames;
     procedure FrameRefreshHandler(sender: TObject);
   ...
   end;
   
procedure TForm1.CreateFrames;
var
  f: TMyFrame;
begin
  f := TMyFrame.Create(self);
  f.OnRefresh := @FrameRefreshHandler;
  f.Parent := PageControl.Pages[0];
  PageControl.Pages[0].Tab := PtrUInt(f);
end;

procedure TForm1.FrameRefreshHandler(Sender: TObject);
begin
  if (Sender is TMyFrame) then
    MyFrame.RefreshFrame;
end;

procedure TMyFrame.RefreshFrame;
begin
  // ... irgendwas
end;

charlytango
Beiträge: 671
Registriert: Sa 12. Sep 2015, 12:10
OS, Lazarus, FPC: Laz 2.2.4
CPU-Target: Win 32Bit, 64bit
Wohnort: Wien

Re: Mehrere Frames aktualisieren (FindComponent)

Beitrag von charlytango »

erstmal Danke -- werde mich damit beschäftigen und berichten ;-)

wp_xyz
Beiträge: 4501
Registriert: Fr 8. Apr 2011, 09:01

Re: Mehrere Frames aktualisieren (FindComponent)

Beitrag von wp_xyz »

Oder so: Alle Frames haben einen gemeinsamen Vorfahren, der eine virtuelle Methode RefreshFrame hat. Nicht refresh-fähige Frames lassen diese Methode leer, refresh-fähige Frames enthalten dort den Code dafür, was im Fall des Refresh zu tun ist. Wenn RefreshFrame public ist, kann sowohl das Formular als auch jeder Frame deinen Refresh eines anderen Frames aufrufen. Wenn RefreshFrame nicht public ist, kannst du den Umweg über das Event im vorigen Beitrag gehen.

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 5783
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Niederösterreich
Kontaktdaten:

Re: Mehrere Frames aktualisieren (FindComponent)

Beitrag von af0815 »

wp_xyz hat geschrieben:
Mo 3. Okt 2022, 12:46
charlytango hat geschrieben:
Mo 3. Okt 2022, 09:50
Demnach brauche ich irgend eine Art Kommunikation entweder zwischen den Frames direkt
oder eine Kommunikation des Frames mit dem TForm das seinerseits das richtige Frame sucht und den Refresh auslöst. Denke da vermutlich viel zu kompliziert grrrr
Puhhh :(
Zum Finden der Frames würde ich den Zeiger auf den Frame einfach im Tag der TabSheets abspeichern. Dann kannst du im PageControl.OnChange aus dem pageControl.ActivePage.Tag einfach den neuen aktiven Frame auslesen.
Ich mache es generell so, das das Frame dann einen Callback bedient der in den Parent oder die ParentForm geht, diese kann dann den Event direkt an das Form weitergeben das den Event benötigt. Oder man verwendet einen Observer ( TPersistent ) Dann wird jeder verständigt der sich an dem Observer angemeldet hat.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

charlytango
Beiträge: 671
Registriert: Sa 12. Sep 2015, 12:10
OS, Lazarus, FPC: Laz 2.2.4
CPU-Target: Win 32Bit, 64bit
Wohnort: Wien

Re: Mehrere Frames aktualisieren (FindComponent)

Beitrag von charlytango »

wp_xyz hat geschrieben:
Mo 3. Okt 2022, 13:08
Oder so: Alle Frames haben einen gemeinsamen Vorfahren, der eine virtuelle Methode RefreshFrame hat. Nicht refresh-fähige Frames lassen diese Methode leer, refresh-fähige Frames enthalten dort den Code dafür, was im Fall des Refresh zu tun ist. Wenn RefreshFrame public ist, kann sowohl das Formular als auch jeder Frame deinen Refresh eines anderen Frames aufrufen. Wenn RefreshFrame nicht public ist, kannst du den Umweg über das Event im vorigen Beitrag gehen.
Ich komme wie zugesagt etwas spät aber doch meiner Berichtspflicht nach ;-)
Bin dem obigen Konzept gefolgt und etwas weiter gegangen.
Die Frames haben nun einen gemeinsamen Vorfahren in dem die Suche abgehandelt wird.
Den Namen des zu aktualisierenden Frames kenn ich ja.

Die eigentliche Suche hab ich in den Vorfahren gepackt und das Frame das die Aktion anstößt sucht sich das Zielframe selbst weil alle am gleichen Owner hängen und jedes frame kennt seinen Owner ja.

Code: Alles auswählen

procedure TMyFrame.RefreshAnyFrame(FrameNameToRefresh: string);
var
  c:TComponent;
begin
  c:=self.Owner.FindComponent(FrameNameToRefresh);  //<< self does the Trick
  if (c is TMyFrame) then
     TMyFrame(c).MyRefresh
  else
    ShowMessage('Oops - falsche Komponente erwischt. Irgendwas ist faul im Frame');
end;

Works fine, danke.

Ein aktualisiertes Beispiel habe ich beigelegt.
Dateianhänge
SimpleFrameAppV3_Find.zip
(142.24 KiB) 29-mal heruntergeladen

Antworten