Wie beende ich graziös ein MDI-Formular, das die Ausführung von Code in Delphi hat
-
20-08-2019 - |
Frage
Ich habe eine MDI-Anwendung geschrieben in Delphi 2007.
Wenn der Benutzer verlässt ein Formular in ihr während Code ausgeführt wird es eine Ausnahme verursacht, weil der Code versucht, eine Komponente zu aktualisieren oder ein Objekt verwenden, die mit der Form befreit worden ist.
Gibt es trotzdem kann ich feststellen, ob Code in dem Exit-Ereignisse ausgeführt wird oder ist es eine Standardmethode mit dieser Situation umgehen?
Update mit mehr Information
Die Ausnahme in der Regel im folgenden Umständen etwas passieren.
Eine Taste auf der Kind mdi Form gedrückt wird, diese Funktion in Form aktiviert, wird die Funktion in die Datenbank gehen und Abrufen von Daten, es wird dann neu formatieren und sie in einer visuellen Komponente auf dem Formular anzuzeigen ( verwendbar ein TListView).
Wenn der Code eine lange Zeit nimmt auszuführen (sagen wir, wenn es eine Menge von Daten zu verarbeiten) wird der Benutzer das Interesse verlieren und klicken Sie auf die Schaltfläche zum Schließen (die Geschwindigkeit des Codes wurde gearbeitet, um zu versuchen, dies zu vermeiden ).
Der Code innerhalb der Funktion noch ausgeführt wird, auch wenn die Form gehört es sich befreit hat (Der Code ist im privaten Abschnitt des Formulars), jetzt, wenn es trys die visuellen Komponenten aktualisieren sie nicht mehr (wie sie existieren wurden mit der Form befreit), und es wirft eine Ausnahme.
Der Code in dem Kind Form ist benutzbar in einer Schleife, wenn dieses Radfahren Aufzeichnungen passieren, und aktualisieren Sie die Listenansicht entsprechend, die Schleifen-Code enthält, der wie so aussieht
inc(i);
if (i mod 25) = 0 then
begin
StatusPnl.Caption := 'Loading ' + intToStr(i) + ', Please wait';
application.ProcessMessages;
end;
Weitere Codebeispiele
das fromClose Ereignis sieht so aus
//Snip
if (Not (Owner = nil)) then
with (Owner as IMainForm)do
begin
//Snip
DoFormFree(Self,Self.Name);
end
else
//Snip
DoFormFree ist eine Funktion im Haupt mdi Mutterform und sieht aus wie so
//Snip
(G_FormList.Objects[x] as TBaseForm).Release;
G_FormList.Objects[i] := nil;
G_FormList.Delete(i);
//Snip
sind alle Formen in einer Liste gespeichert, wie aus verschiedenen Gründen, und alle untergeordneten Formulare erweitern die TBaseForm Klasse.
Im Idealfall möchte ich einen Weg sagen, ob Code in einer Form ausgeführt wird, und verhindert, dass der Benutzer das Formular zu schließen, oder ausblenden, bis der Code abgeschlossen ist, wie in einigen Fällen kann es einen Bericht und Aktualisierung zu erzeugen, wie Statusanzeige, wenn die Ausnahme geschehen, in diesem Fall wird der Bericht unvollständig sein.
als alle Formen sind Unterklassen von TbaseFrom einige globale Art und Weise, dies zu tun wäre ideal, so kann ich den Code an die Grundform hinzufügen und haben es auf alle abstammen Formen arbeiten.
Lösung
Sie bieten nicht genügend Informationen, aber die einfachste Lösung, die den Sinn kommt, ist in der OnCloseQuery Handler zu testen, ob Code ausgeführt wird, und wenn ja CanClose auf False festgelegt.
Alternativ können Sie den Code aus dem MDI-Formular entkoppeln, indem ein Zwischenobjekt zu schaffen, dass sowohl die Form und der Hintergrund-Code kennen. Sie lassen dies einen Verweis auf das Formular, das zurückgesetzt wird, wenn die Form geschlossen ist. Durch Routing alle Zugriff auf das Formular durch dieses Zwischen Objekt Sie die Ausnahmen verhindern.
Edit: Sie benötigen Informationen geben, wie Sie den Code ausführen, der die MDI-Formular zuzugreifen versucht, nachdem er befreit wurde. Es gibt einige Möglichkeiten Arbeiter Code auszuführen, wie:
- in einem Verfahren der Form oder eines anderen Objekts
- in einem OnTimer Event-Handler
- in dem OnIdle-Handler des Application-Objekts
- in einem Hintergrund-Thread
Beachten Sie im ersten Fall, dass das Formular nur befreit werden, wenn Sie es entweder im Code selbst tun, oder wenn Sie anrufen Application.ProcessMessages. Ohne weitere Informationen über das, was wie Ihr Code sieht, kann niemand geben Sie eine bestimmte Antwort auf Ihre Frage.
Edit 2: Mit Ihren zusätzlichen Informationen scheint es, dass der Code in Frage immer in den Verfahren der Form ausgeführt wird. Dies ist einfach durch die Schaffung eines boolean Mitglied zu fangen, die auf True gesetzt, wenn die Ausführung beginnt, und dass auf False gesetzt, wenn die Ausführung beendet hat. Nun müssen Sie nur einen Handler für OnCloseQuery in Ihrer Basisklasse hinzuzufügen, und legen Sie CanClose auf False, wenn das Element (fExecuting zum Beispiel) Wahr ist. Sie können Schließen leise verbieten, oder ein Informationsfeld angezeigt. Ich würde einfach einen Fortschritt Form zeigen oder etwas in der Statusleiste angezeigt, um den Benutzer nicht zu viel mit modalen Infokästen zu unterbrechen.
Was würde ich auf jeden Fall tun wird der Benutzer so dass der lange laufenden Prozess abzubrechen. So könnte man auch ein Meldungsfeld zeigen, fragt die Benutzer, ob sie den Betrieb und in der Nähe abbrechen möchte. Sie müssen noch die Schließung der Form dann überspringen, aber die Anfrage speichern zu schließen, und es verarbeiten, wenn die Ausführung beendet ist.
Andere Tipps
Jede Form hat ein OnCloseQuery Ereignis.
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
Sie können diese verwenden Nähe verschieben von CanClose auf False festlegen.
Sie müssen entscheiden, ob Sie die Nähe zu handhaben wollen, bis die Verarbeitung beendet ist. Oder Sie könnten den Benutzer auffordern wieder zu schließen.
Führen privaten Bereich in MDI-Formular zB. FProcessing
in db Anruf Code tun:
FProcessing := true;
try
i := 0;
if (i mod 25) = 0 then
begin
// do your code
Application.ProcessMessages;
end;
finally
FProcessing := false;
end;
in MDIForm.FormCloseQuery ()
procedure TMDIForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if FProcesing then
CanClose := False;
// or you can ask user to stop fetching db data
end;
Sie sollten ganze app terminaation Aslo überprüfen.
Ich habe ein Objekt, das ein Verfahren oder eine Methode für Sie ohne Verwendung eines Thread ausführen kann. Es verwendet einen Timer, sondern setzt nur einen einfachen Zeilenaufruf. Es unterstützt auch RTTI, so dass Sie einfach in eine Schaltfläche klicken Sie entweder setzen können:
ExecuteMethodProc (MyCode) oder ExecuteMethodName ( 'MyCode');
Viele Grüße, Brian
// Method execution
//-----------------------------------------------------------------------------
type
TArtMethodExecuter = class( TObject )
constructor Create;
destructor Destroy; override;
PRIVATE
FMethod : TProcedureOfObject;
FTimer : TTimer;
FBusy : boolean;
FFreeAfterExecute : boolean;
FHandleExceptions : boolean;
procedure DoOnTimer( Sender : TObject );
procedure SetBusy( AState : boolean );
PUBLIC
procedure ExecuteMethodProc(
AMethod : TProcedureOfObject;
AWait : boolean = False );
procedure ExecuteMethodName(
AMethodObject : TObject;
const AMethodName : string;
AWait : boolean = False );
property FreeAfterExecute : boolean
read FFreeAFterExecute
write FFreeAfterExecute;
property HandleExceptions : boolean
read FHandleExceptions
write FHandleExceptions;
property Busy : boolean
read FBusy;
end;
procedure ExecuteMethodName(
AMethodObject : TObject;
const AMethodName : string;
AHandleExceptions : boolean = True );
// Executes this method of this object in the context of the application.
// Returns immediately, with the method executing shortly.
procedure ExecuteMethodProc(
AMethodProc : TProcedureOfObject;
AHandleExceptions : boolean = True );
// Executes this method of this object in the context of the application.
// Returns immediately, with the method executing shortly.
function IsExecutingMethod : boolean;
// Returns TRUE if we are already executing a method.
// End method execution
//-----------------------------------------------------------------------------
// Method execution
//-----------------------------------------------------------------------------
{ TArtMethodExecuter }
var
iMethodsExecutingCount : integer = 0;
const
wm_ExecuteMethod = wm_User;
constructor TArtMethodExecuter.Create;
begin
Inherited;
end;
destructor TArtMethodExecuter.Destroy;
begin
FreeAndNil( FTimer );
Inherited;
end;
procedure TArtMethodExecuter.DoOnTimer( Sender : TObject );
procedure RunMethod;
begin
try
FMethod
except
on E:Exception do
ArtShowMessage( E.Message );
end
end;
begin
FreeAndNil(FTimer);
try
If Assigned( FMethod ) then
RunMethod
else
Raise EArtLibrary.Create(
'Cannot execute method - no method defined.' );
finally
SetBusy( False );
If FFreeAfterExecute then
Free;
end;
end;
procedure TArtMethodExecuter.SetBusy(AState: boolean);
begin
FBusy := AState;
If AState then
Inc( iMethodsExecutingCount )
else
If iMethodsExecutingCount > 0 then
Dec( iMethodsExecutingCount )
end;
procedure TArtMethodExecuter.ExecuteMethodProc(
AMethod : TProcedureOfObject;
AWait : boolean = False );
begin
SetBusy( True );
FMethod := AMethod;
FTimer := TTimer.Create( nil );
FTimer.OnTimer := DoOnTimer;
FTimer.Interval := 1;
If AWait then
While FBusy do
begin
Sleep( 100 );
Application.ProcessMessages;
end;
end;
procedure TArtMethodExecuter.ExecuteMethodName(AMethodObject: TObject;
const AMethodName: string; AWait: boolean);
var
RunMethod : TMethod;
begin
RunMethod.code := AMethodObject.MethodAddress( AMethodName );
If not Assigned( RunMethod.Code ) then
Raise EArtLibrary.CreateFmt(
'Cannot find method name "%s". Check that it is defined and published.', [AMethodName] );
RunMethod.Data := AMethodObject;
If not Assigned( RunMethod.Data ) then
Raise EArtLibrary.CreateFmt(
'Method object associated with method name "%s" is not defined.', [AMethodName] );
ExecuteMethodProc(
TProcedureOfObject( RunMethod ),
AWait );
end;
procedure ExecuteMethodName(
AMethodObject : TObject;
const AMethodName : string;
AHandleExceptions : boolean = True );
// Executes this method of this object in the context of the application.
// Returns immediately, with the method executing shortly.
var
ME : TArtMethodExecuter;
begin
If IsExecutingMethod then
If AHandleExceptions then
begin
ArtShowMessage( 'A method is already executing.' );
Exit;
end
else
Raise EArtLibrary.Create( 'A method is already executing.' );
ME := TArtMethodExecuter.Create;
ME.FreeAfterExecute := True;
ME.HandleExceptions := AHandleExceptions;
ME.ExecuteMethodName( AMethodObject, AMethodName );
end;
procedure ExecuteMethodProc(
AMethodProc : TProcedureOfObject;
AHandleExceptions : boolean = True );
// Executes this method of this object in the context of the application.
// Returns immediately, with the method executing shortly.
var
ME : TArtMethodExecuter;
begin
If IsExecutingMethod then
If AHandleExceptions then
begin
ArtShowMessage( 'A method is already executing.' );
Exit;
end
else
Raise EArtLibrary.Create( 'A method is already executing.' );
ME := TArtMethodExecuter.Create;
ME.FreeAfterExecute := True;
ME.HandleExceptions := AHandleExceptions;
ME.ExecuteMethodProc( AMethodProc );
end;
function IsExecutingMethod : boolean;
// Returns TRUE if we are already executing a method.
begin
Result := iMethodsExecutingCount > 0;
end;
// End Method execution
//-----------------------------------------------------------------------------
Wenn der Benutzer bis geben will, weil der Vorgang so lange dauert, sie, warum sie nicht erlauben? Ändern Sie den Code leicht zu überprüfen (direkt vor den application.process Nachrichten ist ein guter Ort) ein „will aufhören“ Variable, und wenn es wahr ist, dann von der Schleife zu retten, befreien Sie Ihre Objekte und stornieren. Dann wickeln Sie diese in dem, was dmajkic früher vorgeschlagen.