2009-01-26 9 views
0

J'ai une application MDI écrit en Delphi 2007.Comment puis-je sortir avec grâce un formulaire MDI qui a l'exécution de code dans Delphi

Si l'utilisateur quitte une forme en son sein tout le code est exécuté, il provoque une exception, parce que la code essaye de mettre à jour un composant ou utilise un objet qui a été libéré avec le formulaire.

Y at-il de toute façon que je sache si le code s'exécute dans l'événement exit ou existe-t-il un moyen standard de gérer cette situation?

Mise à jour avec plus d'infomation

L'exception se produit habituellement dans les circonstances suivantes.

Un bouton sur la forme enfant mdi est pressé, ceci active une fonction dans le formulaire, la fonction ira à la base de données et récupérera des données, elle formatera ensuite et affichera dans un composant visuel sur le formulaire (utilisable un TListView). Si le code prend beaucoup de temps à s'exécuter (par exemple s'il y a beaucoup de données à traiter), l'utilisateur perd son intérêt et clique sur le bouton de fermeture (la vitesse du code a été travaillée pour essayer d'éviter ce).

Le code dans la fonction est toujours en cours d'exécution même si la forme à laquelle il appartient a été libérée (le code est dans la section privée du formulaire), maintenant lorsqu'il essaie de mettre à jour les composants visuels. ils ont été libérés avec la forme) et il jette une exception.

Le code sous la forme de l'enfant est usably dans une boucle lorsque cela arrive, les dossiers de cyclisme et mettre à jour le listview en conséquence, les boucles contiennent du code qui ressemble si

inc(i); 
if (i mod 25) = 0 then 
begin 
    StatusPnl.Caption := 'Loading ' + intToStr(i) + ', Please wait'; 
    application.ProcessMessages; 
end; 

D'autres exemples de code

l'événement fromClose ressemble si

//Snip 
if (Not (Owner = nil)) then 
with (Owner as IMainForm)do 
begin 
    //Snip 
    DoFormFree(Self,Self.Name); 
end 
else 
//Snip 

DoFormFree est une fonction sous la forme principale mère un IMD d ressemble donc

//Snip 
(G_FormList.Objects[x] as TBaseForm).Release; 
G_FormList.Objects[i] := nil; 
G_FormList.Delete(i); 
//Snip 

Tous les formulaires sont stockés dans une liste, comme pour des raisons diverses, et toutes les formes de l'enfant étendre la classe TBaseForm. Idéalement, je voudrais savoir comment le code d'un formulaire est en cours d'exécution et empêcher l'utilisateur de le fermer, ou le masquer jusqu'à ce que le code soit terminé, car dans certains cas il peut générer un rapport et mettre à jour en tant que panneau d'état lorsque l'exception se produit, dans ce cas le rapport sera incomplet.

comme tous les formulaires sont sous-classes de TbaseFrom une façon globale de le faire serait idéal, donc je peux ajouter le code à la forme de base et le faire fonctionner sur toutes les formes descendantes.

Répondre

4

Vous ne fournissez pas suffisamment d'informations, mais la solution la plus simple qui vient à l'esprit est de tester dans le gestionnaire OnCloseQuery si le code est en cours d'exécution et, si tel est le cas, de définir CanClose sur False.

Vous pouvez également dissocier le code du formulaire MDI en créant un objet intermédiaire connu à la fois du formulaire et du code d'arrière-plan. Vous laissez ceci avoir une référence au formulaire, qui est réinitialisé lorsque le formulaire est fermé. En acheminant tous les accès au formulaire via cet objet intermédiaire, vous pouvez empêcher les exceptions.

Modifier: Vous devez fournir des informations sur la façon dont vous exécutez le code qui tente d'accéder au formulaire MDI après sa libération. Il y a des façons d'exécuter du code des travailleurs, comme:

  • dans une méthode de forme ou d'un autre objet
  • dans un gestionnaire d'événements OnTimer
  • dans le gestionnaire OnIdle de l'objet Application
  • dans un fil d'arrière-plan

Notez que dans le premier cas, le formulaire ne peut être libéré que si vous le faites vous-même dans le code ou si vous appelez Application.ProcessMessages. Sans plus d'informations sur votre code, personne ne peut vous donner une réponse précise à votre question.

Édition 2: Avec vos informations supplémentaires, il semble que le code en question est toujours exécuté dans les méthodes du formulaire. C'est facile à attraper en créant un membre booléen qui a la valeur True lorsque l'exécution commence, et qui est défini sur False lorsque l'exécution est terminée. Vous n'avez plus qu'à ajouter un gestionnaire pour OnCloseQuery dans votre classe de base et définir False si le membre (fExecuting par exemple) est True. Vous pouvez interdire silencieusement la fermeture ou afficher une boîte d'information. Je montrerais simplement un formulaire de progression ou afficherais quelque chose dans la barre d'état, afin de ne pas trop interrompre l'utilisateur avec des boîtes d'informations modales.

Ce que je ferais certainement est de permettre à l'utilisateur d'annuler le long processus en cours. Ainsi, vous pouvez également afficher une boîte de message demandant à l'utilisateur s'il souhaite annuler l'opération et la fermer. Vous devez toujours ignorer la fermeture du formulaire, mais pouvez stocker la demande de fermeture et la traiter une fois l'exécution terminée.

+0

Merci, quoi d'autre dois-je fournir? – Re0sless

0

Chaque formulaire a un événement OnCloseQuery. Vous pouvez utiliser cette fonction pour différer la fermeture en définissant CanClose sur False (Faux).

Vous devez décider si vous souhaitez gérer la fermeture jusqu'à la fin du traitement. Ou vous pourriez demander à l'utilisateur de se refermer.

0

Introduire un champ privé sous forme MDI par ex. FProcessing

dans le code d'appel db faire:

FProcessing := true; 
try 
    i := 0; 
    if (i mod 25) = 0 then 
    begin 
    // do your code 
    Application.ProcessMessages; 
    end; 
finally 
    FProcessing := false; 
end; 

dans MDIForm.FormCloseQuery() do

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; 

Vous devriez vérifier aslo toute terminaation app.

0

J'ai créé un objet qui peut exécuter une procédure ou une méthode pour vous sans utiliser de thread. Il utilise une minuterie mais n'expose qu'un simple appel d'une ligne.Il prend également en charge RTTI de sorte que vous pouvez simplement mettre dans un bouton cliquez sur:

ExecuteMethodProc (MyCode) ou ExecuteMethodName ('MyCode');

Cordialement, 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 
//----------------------------------------------------------------------------- 
0

Si l'utilisateur souhaite abandonner parce que l'opération prend si longtemps, ils pourquoi ne pas leur permettre de trop? Modifiez légèrement votre code pour vérifier (juste avant l'application.processus de messages est un bon endroit) une variable "veut quitter", et quand il est vrai alors pour libérer votre boucle, libérer vos objets et annuler. Alors enveloppez ceci dans ce que Dmajkic a suggéré plus tôt.