![]() |
Immer Ärger mit ARC
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo zusammen,
ich habe in den vergangenen Tagen einen Thread geöffnet und bin in meiner Unwissenheit von einer falschen Idee ausgegangen. Jetzt will ich's nochmal neu angehen und frage hier mal in die Landschaft, ob sich einer aufopfert, mir an der Stelle zu helfen. Ich wollte schön sauber getrennt ein Programm entwerfen, erst mal nur als Test, um zu sehen, wie es läuft (nun, es lief nicht) und habe dies nun entschlankt und nochmal probiert. Die Fehlermeldung ist dieselbe wie immer, ist auch unten als Screenshot angehängt. Im AVD lässt sich das als App aufrufen, der Compiler meldet keinen Fehler. Im Debug-Modus zeigt mir das ganze schon im "program"-Teil Main ab "Presenter := TPresenter.Create(View, Model);", dass View= NIL ist. Ich gehe jetzt mal davon aus, das Stevie recht hat siehe: Zitat:
Ich habe jetzt einfach mal den gesamten Quellcode reingesteckt, dann wird's zwar viel, aber letztlich klar. - Denke ich :? Das Programm
Code:
program AppTwo;
uses System.StartUpCopy, FMX.MobilePreview, FMX.Forms, Viewer in 'Viewer.pas' {View}, MBPresenter in 'MBPresenter.pas', MBModel in 'MBModel.pas', MBInterface in 'MBInterface.pas'; {$R *.res} Procedure Main; Var Model : IMyInterfaceModel; View : TView; Presenter : TPresenter; Begin Application.CreateForm(TView, View); Model := TModel.Create; Presenter := TPresenter.Create(View, Model); try Application.Run; finally Presenter.Free; Model._Release; end; End; begin Application.Initialize; Main; end. Die View
Code:
unit Viewer;
interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, MBInterface, FMX.Objects; type TView = class(TForm, IMyInterfaceView) Text1: TText; private { Private-Deklarationen } public { Public-Deklarationen } Procedure SetHelloWorld (Value : String); end; var View: TView; implementation {$R *.fmx} Procedure TView.SetHelloWorld (Value : String); Begin Text1.Text := Value; End; end. Der Presenter
Code:
unit MBPresenter;
interface uses System.SysUtils, MBInterface; Type TPresenter = Class fView : IMyInterfaceView; fModel : IMyInterfaceModel; public constructor Create(const View: IMyInterfaceView; const Model: IMyInterfaceModel); End; implementation constructor TPresenter.Create(const View: IMyInterfaceView; const Model: IMyInterfaceModel); Begin if not Assigned( View ) then raise System.SysUtils.EArgumentNilException.Create( 'View' ); if not Assigned( Model ) then raise System.SysUtils.EArgumentNilException.Create( 'Model' ); fView := View; fModel := Model; fView.SetHelloWorld(fModel.getHelloWorld); End; end. Das Model
Code:
unit MBModel;
interface uses MBInterface; Type TModel = Class(TInterfacedObject, IMyInterfaceModel) fHelloWorld : String; public constructor create; published Function getHelloWorld : String; End; implementation constructor TModel.create; begin fHelloWorld := 'Hello World'; end; Function TModel.getHelloWorld : String; begin Result := fHelloWorld; end; end. Das Interface
Code:
unit MBInterface;
interface Type IMyInterfaceModel = Interface(IInterface) ['{80AC074A-7D51-49F2-B94D-2716B9CBA938}'] Function getHelloWorld : String; End; Type IMyInterfaceView = Interface(IInterface) ['{D550D27A-57CA-4AAE-809E-60417E7E7DDC}'] Procedure SetHelloWorld (Value : String); End; implementation end. |
AW: Immer Ärger mit ARC
Du hast kein Problem mit ARC sondern mit dem Lesen der Doku
![]() http://docwiki.embarcadero.com/Libraries/de/FMX.Forms.TApplication.CreateForm Erstellt zur Laufzeit ein neues FireMonkey-Formular. Rufen Sie
Delphi-Quellcode:
auf, um ein FireMonkey-Formular dynamisch zur Laufzeit zu erstellen. Bei den meisten Formularen braucht kein eigener Quelltext geschrieben zu werden, da bei Verwendung des Formular-Designers üblicherweise ein oder mehrere Aufrufe von
CreateForm
Delphi-Quellcode:
automatisch in die Quelltextdatei des Projekts eingefügt werden.
CreateForm
Zitat:
Auf deinen Code angewendet sollte das hier die Lösung sein (klein aber fein)
Delphi-Quellcode:
program AppTwo;
uses System.StartUpCopy, FMX.MobilePreview, FMX.Forms, Viewer in 'Viewer.pas' {View}, MBPresenter in 'MBPresenter.pas', MBModel in 'MBModel.pas', MBInterface in 'MBInterface.pas'; {$R *.res} Procedure Main; Var Model : IMyInterfaceModel; View : TView; Presenter : TPresenter; Begin Application.CreateForm(TView, View); Application.RealCreateForms; // <-- da isser der pöse Pursche Model := TModel.Create; Presenter := TPresenter.Create(View, Model); try Application.Run; finally Presenter.Free; // ist eigentlich nicht notwendig, das Model-Interface wird am Ende diese Prozedur // automatisch freigegeben // Model._Release; // alternativ wäre noch ein // Model := nil; // denkbar end; End; begin Application.Initialize; Main; end. |
AW: Immer Ärger mit ARC
Danke dir erst mal,
ich habe das mal ausprobiert. Sagen wir mal so: Es stürzt nicht ab, aber ich fürchte, ich sehe schwarz. Jep, es ist auch nach einer Minute noch schwarz. Auch dann, wenn ich's auf's Smartphone installiere, ist das Resultat dasselbe. zumindest ist der pöse Pursche schon mal lokalisiert. ich habe dann die Form nochmal frisch aufgesetzt, HeaderFooterApplication. Dachte mir, vielleicht ist die leere Anwendung einfach schwarz. Dem ist aber nicht so. |
AW: Immer Ärger mit ARC
Nimm mal das
Delphi-Quellcode:
aus dem
fView.SetHelloWorld
Delphi-Quellcode:
dann sollte die Anwendung schon mal fehlerfrei starten ;)
TPresenter.Create
|
AW: Immer Ärger mit ARC
Hab ich so gemacht, die Anwendung startet, beim Debuggen werden alle Objekte angezeigt.
Auf dem Bildschirm aber tut sich nach wie vor nix. Scharzer Adler auf schwazem Grund. Gestern hab ich mal was anderes ausprobiert, das läuft zwar, ist aber nicht so gestrickt, wie ich das haben möchte. Sprich: Was ich hier an der Stelle erreichen möchte, ist, die Trennung in MVP, um das eine, oder andere austauschbar zu machen. Die Tuts, die ich bislang gefunden habe zeigen zwar was her, erwähnen aber sowas nicht mal ansatzweise. Und du hast recht, in der Doku habe ich wirklich nicht nachgeschaut. Das liegt aber nicht an einer Lesefaulheit, sondern schlicht daran, dass ich mich auf das falsche konzentriert habe. Den Befehl kannte ich absolut nicht. |
AW: Immer Ärger mit ARC
Und noch mal an alle,
ich habe das Testprojekt nochmal frisch aufgesetzt. Das Interface ist dasselbe wie oben, wurde aber nicht verwendet.
Code:
program AppThree;
uses System.StartUpCopy, FMX.MobilePreview, FMX.Forms, HeaderFooterTemplate in 'HeaderFooterTemplate.pas' {View}, MBInterface in '..\TryAppTwo\MBInterface.pas'; {$R *.res} Procedure Main; Var View : TView; Begin Application.CreateForm(TView, View); Application.RealCreateForms; try Application.Run; finally end; End; begin Application.Initialize; Main; end.
Code:
unit HeaderFooterTemplate;
interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls, MBInterface; type TView = class(TForm) Header: TToolBar; Footer: TToolBar; HeaderLabel: TLabel; private { Private declarations } public { Public declarations } Procedure SetHelloWorld (Value : String); end; var View: TView; implementation {$R *.fmx} Procedure TView.SetHelloWorld (Value : String); Begin End; ohne "Application.RealCreateForms; " geht es, mit, geht es nicht mehr, der Bildschirm bleibt schwarz. Dabei habe ich aber den gesamten Businescode weggelassen. Zu den Interface-Einbindungen, geschweige den Einbindungen für den Presenter / Model bin ich gar nicht gekommen. Funktioniert das denn bei Apps für Android so anders? Rethorisch: Auf was muss ich mich den dann noch bei IOs / MacOSx einlassen? |
AW: Immer Ärger mit ARC
So ich habe hier mal eine Version, die jetzt auch mit FMX und Windows/Android funktioniert. Grundproblem ist hier die Besonderheit der MainForm (Basis der gesamten Anwendung) und dem Startverhalten bei den Mobile-Plattformen. Dadurch kommt es nur bei der MainForm zum Bruch der ganz strikten Trennung.
Delphi-Quellcode:
unit MVP.Base;
interface type IView = interface ['{7B9FA290-778D-4E2F-8112-92ECACBEBB5A}'] end; IModel = interface ['{E690A24A-C5D2-47B5-9DBC-45F7695AE167}'] end; IPresenter = interface ['{A3BA4F9C-7E67-4D15-BE4A-514FBE85113B}'] end; TModel = class abstract( TInterfacedObject, IModel ) end; TPresenter<TViewType: IView; TModelType: IModel> = class abstract( TInterfacedObject, IPresenter ) private FView: TViewType; FModel: TModelType; protected property View: TViewType read FView; property Model: TModelType read FModel; public constructor Create( AView: TViewType; AModel: TModelType ); virtual; end; implementation { TPresenter<TViewType, TModelType> } constructor TPresenter<TViewType, TModelType>.Create( AView: TViewType; AModel: TModelType ); begin inherited Create; FView := AView; FModel := AModel; end; end.
Delphi-Quellcode:
unit Interfaces.Main;
interface uses MVP.Base; type IMainView = interface( IView ) ['{02D78A1F-3749-4A60-82A0-747E8F2D65FE}'] procedure setHelloWorld( const Value: string ); end; IMainModel = interface( IModel ) ['{3F9AC82F-2AF6-485D-BF63-279A9C42B4AA}'] function getHelloWorld: string; end; IMainPresenter = interface( IPresenter ) ['{67D69F7B-539F-4124-B204-90A03973E26D}'] end; implementation end.
Delphi-Quellcode:
unit Presenter.Main;
interface uses MVP.Base, Interfaces.Main; type TMainPresenter = class( TPresenter<IMainView, IMainModel>, IMainPresenter ) public procedure AfterConstruction; override; end; implementation { TMainPresenter } procedure TMainPresenter.AfterConstruction; begin inherited; View.setHelloWorld( Model.getHelloWorld ); end; end.
Delphi-Quellcode:
unit Model.Main;
interface uses MVP.Base, Interfaces.Main; type TMainModel = class( TModel, IMainModel ) public function getHelloWorld: string; end; implementation { TMainModel } function TMainModel.getHelloWorld: string; begin Result := 'Hello World!'; end; end.
Delphi-Quellcode:
unit Form.Main;
interface uses MVP.Base, System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, Interfaces.Main, FMX.Objects; type TMainForm = class( TForm, IView, IMainView ) Text1: TText; private FPresenter: IPresenter; procedure setHelloWorld( const Value: string ); public procedure AfterConstruction; override; end; var MainForm: TMainForm; implementation {$R *.fmx} uses Presenter.Main, Model.Main; { TMainForm } procedure TMainForm.AfterConstruction; begin inherited; // Hier der Bruch, denn die View erstellt sich selbst den Presenter // Das könnte man auch noch auslagern mit einer Automatik, wo // der passende Presenter zur View erzeugt wird FPresenter := TMainPresenter.Create( Self, TMainModel.Create ); end; procedure TMainForm.setHelloWorld( const Value: string ); begin Text1.Text := Value; end; end.
Delphi-Quellcode:
Die Unit
program dp_182444;
uses System.StartUpCopy, FMX.Forms, Form.Main in 'Form.Main.pas' {MainForm}, Interfaces.Main in 'Interfaces.Main.pas', Model.Main in 'Model.Main.pas', MVP.Base in 'MVP.Base.pas', Presenter.Main in 'Presenter.Main.pas'; {$R *.res} begin ReportMemoryLeaksOnShutdown := True; Application.Initialize; Application.CreateForm(TMainForm, MainForm); Application.Run; end.
Delphi-Quellcode:
ist eigentlich nur Spielerei :)
MVP.Base
|
AW: Immer Ärger mit ARC
Zitat:
Delphi-Quellcode:
// registrieren
if TPlatformServices.Current.SupportsPlatformService( IFMXApplicationEventService, FApplicationEventService ) then begin FApplicationEventService.SetApplicationEventHandler( Self.ApplicationEventHandler ); end; // Handler function TMain_Form.ApplicationEventHandler( AAppEvent: TApplicationEvent; AContext: TObject ): Boolean; begin case AAppEvent of TApplicationEvent.FinishedLaunching: TApp.Log( '[AppEvent] FinishedLaunching' ); TApplicationEvent.BecameActive: TApp.Log( '[AppEvent] BecameActive' ); TApplicationEvent.WillBecomeInactive: TApp.Log( '[AppEvent] WillBecomeInactive' ); TApplicationEvent.EnteredBackground: TApp.Log( '[AppEvent] EnteredBackground' ); TApplicationEvent.WillBecomeForeground: TApp.Log( '[AppEvent] WillBecomeForeground' ); TApplicationEvent.WillTerminate: TApp.Log( '[AppEvent] WillTerminate' ); TApplicationEvent.LowMemory: TApp.Log( '[AppEvent] LowMemory' ); TApplicationEvent.TimeChange: TApp.Log( '[AppEvent] TimeChange' ); TApplicationEvent.OpenURL: TApp.Log( '[AppEvent] OpenURL' ); end; Result := True; end; |
AW: Immer Ärger mit ARC
Sir Rufo, ich danke euch :wink:
darf ich das erst mal verdauen? Ich kann noch nicht behaupten, dass ich das so verstehe. Design-Pattern, auch Interfaces und letztlich nun auch die Umsetzung für Smartphones ist mir noch zu neu. Das mit MVP wurde mir ja von Stevie super erklärt. Hat sich echt Mühe gegeben. Bei dir sieht das ja doch etwas anders aus. hach - und jetzt beantwortest du auch schon die nächste Frage. Und wenn ich mir jetzt auch klein und dämlich vorkomme :oops: könntest du mir jetzt bitte auch noch erklären, wo genau dieser Code unterkommt? Hier habe ich jetzt gar nix verstanden. |
AW: Immer Ärger mit ARC
Den Code mit den Application-Events meinst du?
Der kommt normalerweise in die MainForm, allerdings würde der hier besser im MainPresenter passen, denn der soll ja die Kontrolle über die View haben. Allerdings sehen wir ja, dass das dann eigentlich schon zu spät sein könnte. Denkbar wäre jetzt auch noch ein ApplicationPresenter (?) der diese Events empfängt und dann auch für das Zusammenstöpseln von MainView, MainModel und MainPresenter sorgen kann. Ob das schön ist kann ich noch nicht sagen, ist mir nur gerade so in den Kopf gekommen und könnte wieder für die saubere Trennung sorgen. |
AW: Immer Ärger mit ARC
Ok, Sir Rufo,
nochmals Danke für den Vorschlag. Wenn ich die erste Portion verstanden habe, werde ich mich um den zuletzt genannten kümmern. Jetzt möchte ich dich auch nicht länger belästigen, das war schon toll genug. |
AW: Immer Ärger mit ARC
Liste der Anhänge anzeigen (Anzahl: 1)
Im Anhang mal das ganze Projekt.
Zu den Änderungen: Eine zentrale Instanz
Delphi-Quellcode:
bekommt eine anonyme Factory mitgegeben um den Presenter zu erzeugen.
TAppStarter
Aufgerufen wird diese Factory dann bei den Mobile-Plattformen (IOS/ANDROID) über den ![]() ![]() ![]()
Delphi-Quellcode:
(die ruft dann die Factory auf).
TAppStarter.CreateMainPresenter
Delphi-Quellcode:
und die Projekt-Datei
unit App.Starter;
interface {$INCLUDE 'Conditional.inc'} uses System.SysUtils, FMX.Platform; type TAppStarter = class private FPresenterFactory: TFunc<IInterface>; FMainPresenter: IInterface; FApplicationEventService: IFMXApplicationEventService; procedure GetPlatformServices; function ApplicationEventHandler( AAppEvent: TApplicationEvent; AContext: TObject ): Boolean; {$IFDEF MOBILE} private // bei Mobile-Platform verstecken {$ELSE} public // sonst öffentlich aufrufbar {$ENDIF} procedure CreateMainPresenter; public constructor Create( PresenterFactory: TFunc<IInterface> ); end; implementation { TAppStarter } function TAppStarter.ApplicationEventHandler( AAppEvent: TApplicationEvent; AContext: TObject ): Boolean; begin if AAppEvent = TApplicationEvent.FinishedLaunching then CreateMainPresenter; Result := True; end; constructor TAppStarter.Create( PresenterFactory: TFunc<IInterface> ); begin inherited Create; FPresenterFactory := PresenterFactory; GetPlatformServices; end; procedure TAppStarter.CreateMainPresenter; begin if not Assigned( FMainPresenter ) then FMainPresenter := FPresenterFactory( ); end; procedure TAppStarter.GetPlatformServices; begin if TPlatformServices.Current.SupportsPlatformService( IFMXApplicationEventService, FApplicationEventService ) then FApplicationEventService.SetApplicationEventHandler( Self.ApplicationEventHandler ); end; end.
Delphi-Quellcode:
program dp_182444;
{$INCLUDE 'Conditional.inc'} uses System.StartUpCopy, FMX.Forms, Form.Main in 'Form.Main.pas' {MainForm} , Interfaces.Main in 'Interfaces.Main.pas', Model.Main in 'Model.Main.pas', MVP.Base in 'MVP.Base.pas', Presenter.Main in 'Presenter.Main.pas', App.Starter in 'App.Starter.pas'; {$R *.res} procedure Main; var LAppStarter: TAppStarter; begin Application.Initialize; LAppStarter := TAppStarter.Create( function: IInterface var LPresenter: IMainPresenter; begin LPresenter := TMainPresenter.Create( MainForm, TMainModel.Create ); LPresenter.SetAppStarter( LAppStarter ); Result := LPresenter; end ); try Application.CreateForm( TMainForm, MainForm ); {$IFNDEF MOBILE} Application.RealCreateForms; LAppStarter.CreateMainPresenter; {$ENDIF} Application.Run; finally LAppStarter.Free; end; end; begin ReportMemoryLeaksOnShutdown := True; Main; end. |
AW: Immer Ärger mit ARC
Zitat:
Code:
program AppTwo;
... Procedure Main; Var Model : IMyInterfaceModel; View : TView; Presenter : TPresenter; Begin Application.CreateForm(TView, View); Model := TModel.Create; Presenter := TPresenter.Create(View, Model); try Application.Run; finally Presenter.Free; Model._Release; end; End; ...
Code:
Du hast eine lokale Variable "View" in der Prozedur Main und eine gleichnamige globale in der Unit Viewer. Eine von beiden ist wohl NIL.
unit Viewer;
... var View: TView; ... Gruß, Olli |
AW: Immer Ärger mit ARC
Durch den Scope ist aber klar welche Variable hier benutzt wird, nämlich die lokale. Die globale wird immer auf
Delphi-Quellcode:
bleiben und die lokale ist aber auch nach
nil
Delphi-Quellcode:
auf
Application.CreateForm (TView, View);
Delphi-Quellcode:
. Das steht auch in der Doku so.
nil
Die Vorgehensweise ist prinzipiell schon richtig, da die richtige Variable benutzt wird, allerdings gibt es ein Problem mit dem Timing! |
AW: Immer Ärger mit ARC
Hallo Sir Rufo,
vielen Dank, ich habe mir dein Beispiel runtergeladen und versuche mich nun da durchzuwursteln. Das Beispiel, das du mir gestern geliefert hast läuft Fehlerfrei. Auf dem AVD dauert es zwar zw. 10 und 15 Sekunden, auf dem Smartphone geht es deutlich schneller, ca. 3-4 Sec. Damit bin ich schon mehr als zufrieden. Nochmal vielen Dank dafür. Und jetzt kommst du sogar nochmal mit einer anderen Lösung. -Wow. auch das muss mal in aller Kürze gesagt werden. Lass es gut sein, damit werde ich zurechtkommen. Du hast mehr geholfen als ich erwartet hätte. Btw: ich komme bestimmt mit neuen Fragen wieder. |
AW: Immer Ärger mit ARC
Da habe ich doch gerade herausgefunden, dass man vom
![]() Logischer-(Emba-)weise wird in der versendeten Message nicht die Form-Instanz mitgegeben (nachher kann man noch damit etwas sinnvolles anfangen, das muss sofort unterbunden werden :roll:). Als
Delphi-Quellcode:
bekommt man dafür die
Sender
Delphi-Quellcode:
-Instanz geliefert. Die ist auch wichtig ... da kommt man immer so schwer dran ... aber im Kontext gesehen ist das richtig, denn
Application
Delphi-Quellcode:
schickt die Nachricht auf die Reise. Na gut.
Application
Delphi-Quellcode:
Ach ja, nicht verwirren lassen, dass die Nachricht
TMessageManager.DefaultManager.SubscribeToMessage(
TFormsCreatedMessage, procedure ( const Sender : TObject; const AMessage : TMessage ) begin // hier irgendwas sinnvolles machen end );
Delphi-Quellcode:
lautet und jeder aufmerksame Leser da eine Nachricht vermutet, die verschickt wird, wenn alle Forms Created sind. Nein, die kommt logischerweise bei jeder Form (die über
TFormsCreatedMessage
Delphi-Quellcode:
erzeugt wird).
TApplication.CreateForm
Irgendwie beschleicht mich das Gefühl, die haben sich da vertan und wollten die Nachricht am Ende der Schleife verschicken (da würde der Message-Name einen Sinn ergeben und auch das Fehlen der Form-Instanz) |
Alle Zeitangaben in WEZ +1. Es ist jetzt 15:52 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz