Check alle échte Black Friday-deals Ook zo moe van nepaanbiedingen? Wij laten alleen échte deals zien
Toon posts:

[Delphi] Access violations bij sluiten modal form

Pagina: 1
Acties:

Verwijderd

Topicstarter
Om gebruikers automatisch uit te loggen na een x tijd inactiviteit gebruik ik een timer die een message naar 't MainForm post. De message handler roept vervolgens LogoffUser aan:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
procedure TfrmMain.CloseChildForms(AForm: TForm);
var
  i: integer;
begin
  for i := 0 to AForm.ComponentCount - 1 do begin
    if (AForm.Components[i] is TForm) and (TForm(AForm.Components[i]).FormStyle <> fsMDIChild) then begin
      CloseChildForms(TForm(AForm.Components[i]));
      TForm(AForm.Components[i]).OnCloseQuery := nil;
      TForm(AForm.Components[i]).OnClose := nil;
      TForm(AForm.Components[i]).ModalResult := mrAbort;
    end;
  end;
end;

procedure TfrmMain.LogoffUser;
var
  i: integer;
begin
  // logout inactive user
  UserInfo.UserName := '';
  // close forms
  CloseChildForms(Self);
  for i := 0 to MdiChildCount - 1 do begin
    CloseChildForms(MdiChildren[i]);
    MdiChildren[i].OnCloseQuery := nil;
    MdiChildren[i].Close;
  end;
  QuickLogout(miQuickLogout);
end;

Dit werkt prima, totdat er op dat moment een modal form openstaat die 1 van de MDIChildren als owner heeft. Wanneer 't MainForm de owner is, is er niets aan de hand.
Probleem is dat de Release van 't modal form pas uitgevoerd wordt nadat de QuickLogout routine is afgehandeld (deze wist de current user en toont een login schermpje). Beetje vreemd, want in de code worden de modal forms als eerste gesloten.
Oorzaak van de access violation is dat op het moment van releasen de handle naar het window niet meer bestaat.

Application.ProcessMessages na het sluiten van het modal form helpt niets, en ook een PostMessage van WM_CLOSE of CM_DEACTIVATE of DestroyWindow bieden geen soelaas.

Iemand enig idee?

  • pedorus
  • Registratie: Januari 2008
  • Niet online
CloseChildForms doet geen .Close? Wat maakt fsMDIChild's eigenlijk anders? Waarom
UserInfo.UserName := ''; niet in Quicklogout?

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Verwijderd

Topicstarter
pedorus schreef op donderdag 15 mei 2008 @ 10:27:
CloseChildForms doet geen .Close? Wat maakt fsMDIChild's eigenlijk anders? Waarom
UserInfo.UserName := ''; niet in Quicklogout?
ModalResult op een andere waarde dan mrNone zetten zorgt ervoor dat een modal form wordt gesloten. Uit TCustomForm.ShowModal:
Delphi:
1
2
3
4
5
6
7
      ModalResult := 0;
      repeat
        Application.HandleMessage;
        if Application.FTerminate then ModalResult := mrCancel else
          if ModalResult <> 0 then 
            CloseModal;
      until ModalResult <> 0;

UserInfo.UserName wordt op die plek leeggemaakt omdat de OnClose routines van de MDIChildren dit checken. Wanneer er geen UserName is worden wijzigingen bv. niet opgeslagen.

  • IWriteCode
  • Registratie: Juli 2000
  • Laatst online: 18-09 13:34

IWriteCode

Less = more

En wat gebeurt er als je de owner van het modal form op nil zet?

code:
1
2
3
4
      TForm(AForm.Components[i]).Owner:= nil; 
      TForm(AForm.Components[i]).OnCloseQuery := nil; 
      TForm(AForm.Components[i]).OnClose := nil; 
      TForm(AForm.Components[i]).ModalResult := mrAbort;


En de release gebeurt waarschijnlijk pas later omdat de applicatie dat pas doet als het de windows messages weer gaat verwerken...

Alternatief kan ook nog zijn om zelf de modal forms te gaan closen, ipv het ModalResult te gebruiken?

[ Voor 28% gewijzigd door IWriteCode op 15-05-2008 10:48 ]

Less = more


Verwijderd

Topicstarter
Jamal schreef op donderdag 15 mei 2008 @ 10:47:
En wat gebeurt er als je de owner van het modal form op nil zet?
Owner is een readonly property, en
Delphi:
1
AForm.Components[i].Owner.RemoveComponent(AForm.Components[i]);

levert een List index out of bounds error op...
En de release gebeurt waarschijnlijk pas later omdat de applicatie dat pas doet als het de windows messages weer gaat verwerken...
Daar lijkt het wel op, maar het vreemde is dat in de tussentijd meerdere messages verwerkt zijn: de MDIChildren zijn bv. al wel gereleased (dat gebeurt via een PostMessage) en het inlogscherm getoond en ook weer gesloten.
Alternatief kan ook nog zijn om zelf de modal forms te gaan closen, ipv het ModalResult te gebruiken?
Helaas, zelfde resultaat, net als een WM_CLOSE ofCM_DEACTIVATE message of DestroyWindow.

  • LordLarry
  • Registratie: Juli 2001
  • Niet online

LordLarry

Aut disce aut discede

Zoals je ziet zit een ShowModal in een loop waar die pas uit gaat als er weer een message af te handelen is. Application.ProcessMessages aanroepen helpt niet, want die heeft zijn eigen loop. Je zal er dus voor moeten zorgen dat de ShowModal code weer een loopje kan maken en er dan uit kan vallen. Dat kan je doen door nadat ModalResult op mrAbort heb gezet een message naar jezelf te posten en pas bij het afhandelen van die message de overgebleven schermen sluiten via Close.

We adore chaos because we like to restore order - M.C. Escher


  • pedorus
  • Registratie: Januari 2008
  • Niet online
Er zijn geen andere forms dan ModalForms waarvoor CloseChildForms bedoeld is? Noem het dan CloseModalForms zou ik zeggen.

Toch zou zelf .Close/CloseModal/oid aanroepen hier best eens kunnen werken, omdat dan de parent nog bestaat. Later bij het 'automatisch' aanroepen (als Application.HandleMessage terugkomt) is dat niet meer zo.
Verwijderd schreef op donderdag 15 mei 2008 @ 11:05:

Delphi:
1
AForm.Components[i].Owner.RemoveComponent(AForm.Components[i]);

levert een List index out of bounds error op...
Moet zijn
Delphi:
1
AForm.Components[i].Owner.RemoveComponent(AForm);

?
En eventueel dan frmMain als Owner toevoegen.

[ Voor 36% gewijzigd door pedorus op 15-05-2008 11:25 ]

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


  • IWriteCode
  • Registratie: Juli 2000
  • Laatst online: 18-09 13:34

IWriteCode

Less = more

Verwijderd schreef op donderdag 15 mei 2008 @ 11:05:
Owner is een readonly property, en
Delphi:
1
AForm.Components[i].Owner.RemoveComponent(AForm.Components[i]);

levert een List index out of bounds error op...
Dat komt omdat je een item uit de lijst haalt, maar dat de for loop wel door telt naar de oude count. Probeer anders:
code:
1
  for i := AForm.ComponentCount - 1 downto 0 do begin

Less = more


Verwijderd

Topicstarter
Jamal, dat klopt en daar was ik al achter. Alleen helpt RemoveComponent/InsertComponent niets, de foutmelding blijft...
CloseChildForms ziet er nu zo uit:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
procedure TfrmMain.CloseChildForms(AForm: TForm);
var
  i: integer;
  frm: TForm;
begin
  for i := AForm.ComponentCount - 1 downto 0 do begin
    if (AForm.Components[i] is TForm) and (TForm(AForm.Components[i]).FormStyle <> fsMDIChild) then begin
      frm := TForm(AForm.Components[i]);
      CloseChildForms(frm);
      AForm.RemoveComponent(frm);
      InsertComponent(frm);
      frm.OnCloseQuery := nil;
      frm.OnClose := nil;
      frm.ModalResult := mrAbort;
      PostMessage(frm.Handle, CM_DEACTIVATE, 0, 0);
      frm.Close;
    end;
  end;
end;


@pedorus, CloseModal is een private method van TCustomForm, dus die kun je niet zomaar aanroepen.

  • IWriteCode
  • Registratie: Juli 2000
  • Laatst online: 18-09 13:34

IWriteCode

Less = more

Tja... debuggen zonder code erbij blijft lastig. Wat gebeurt er als je Close gebruikt ipv die mrAbort?

En als iets niet werkt, dan kan je niet zo goed de wijzigingen die je nu toe hebt gevoegd weer weghalen...

Alternatief is natuurlijk bij het creeeren van de (modal) forms geen Owner mee te geven... en alleen een referentie gebruiken...

Less = more


  • pedorus
  • Registratie: Januari 2008
  • Niet online
Ik heb Delphi niet bij de hand, maar wellicht dat de Parent ook nog veranderd moet worden.

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Verwijderd

Topicstarter
Ik heb 't een beetje opgegeven... In eerste instantie lee het vervangen van de OnClose door een procedure die alleen 'Action := caFree' deed te werken, maar dan vallen er allerlei andere dingen om.
Die modal forms worden vrijwel allemaal gebruikt via:
Delphi:
1
2
3
4
5
6
7
8
with MyModalForm.Create(Self) do try
  // voed eventueel data aan 't form
  if ShowModal = mrOK then begin
    // doe iets met de data van 't form
  end;
finally
  Release;
end;

Al gaat 't closen van 't form dan prima, dan is 't stuk na de ShowModal daarna de weg kwijt, omdat 't form en/of 't aanroepende MDIChild form dan niet meer bestaat.
Voorlopig "opgelost" door LogoffUser te laten exiten wanneer er nog modal forms aanwezig zijn, maar een echte oplossing is 't niet. Al is 't wel voldoende voor PCI compliance. :)

Een echte oplossing vergt een flinke rewrite van de applicatie (een interface op alle modal forms en een FreeNotification-achtige message naar 't MDI form dat 'm heeft aangeroepen), maar bij een applicatie met een paar 100.000 regels code is dat niet zo 1-2-3 aangepast...

  • IWriteCode
  • Registratie: Juli 2000
  • Laatst online: 18-09 13:34

IWriteCode

Less = more

Wat wij altijd gebruikten was een soort FormController. Deze handelde dan een compleet form af. Deze regelde ook het creeeren en weer vrijgeven van een scherm. Deze controller creeerde je dan, en die releasde zichzelf als het form klaar was.

Ff code zoeken die erbij hoorde...

Hmm... hmm... ben benieuwd of dat trouwens iets gaat helpen...

En het anders in twee stappen doen? Eerst sloop je alle modal forms, en daarna de mdi forms?

Dus je voorziet elk modal form van een message handler die specifiek luistert naar een bepaalde windows message. Deze sluit dan het modal form. Als dit allemaal gedaan is, dan post je vervolgens een message om alle mdi forms te closen...

[ Voor 41% gewijzigd door IWriteCode op 15-05-2008 22:22 ]

Less = more


Verwijderd

Topicstarter
Jamal schreef op donderdag 15 mei 2008 @ 22:13:
En het anders in twee stappen doen? Eerst sloop je alle modal forms, en daarna de mdi forms?

Dus je voorziet elk modal form van een message handler die specifiek luistert naar een bepaalde windows message. Deze sluit dan het modal form. Als dit allemaal gedaan is, dan post je vervolgens een message om alle mdi forms te closen...
Op zoiets was ik zelf ook al gekomen...
Een echte oplossing vergt een flinke rewrite van de applicatie (een interface op alle modal forms en een FreeNotification-achtige message naar 't MDI form dat 'm heeft aangeroepen), maar bij een applicatie met een paar 100.000 regels code is dat niet zo 1-2-3 aangepast...
Maar bij een bestaande (en vrij grote) applicatie is dat een flinke ingreep, en wanneer je je klanten niet als betatester wilt gebruiken duurt 't best lang voor je een uitrol kunt doen. :)

En dan nog vang je niet alles af, zoals bv. een standaard MessageDlg van Delphi.

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Eigenlijk werkt de code uit de startpost gewoon denk ik. Het probleem is denk ik dat ergens, waarschijnlijk in QuickLogout een ShowModal/ShowMessage/oid wordt gedaan, waardoor de onderliggende loops en modalforms op de stack blijven. Bij het sluiten van het venster krijg je dan een exception.

Een oplossing daarvoor is om QuickLogout aan te roepen nadat je uit de ShowModal-loops bent, dmv een message. Een alternatief is om QuickLogout geen modal dingen te laten doen. Een inlogvenster kan prima een gemaximaliseerd MDIChild zijn.

In een rewrite zie ik niet echt een oplossing.

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


  • IWriteCode
  • Registratie: Juli 2000
  • Laatst online: 18-09 13:34

IWriteCode

Less = more

Alternatief kan natuurlijk ook zijn, dat je een lijst / stack bij houdt van de modal forms die er actief zijn. Dat zal er altijd maar eentje zijn.. of modal forms die weer andere modal forms aanroepen. Dan voeg je in die modal forms (of de basis klasse daarvan) de mogelijkheid toe voor het sluiten van de modal forms via een windows message. En dan sluit je de modal forms op volgorde van de stack.

Dan blijft de afhandeling volgens mij vrij hetzelfde...

En als je problemen hebt bij het gebruik van de standaard MessageDlg van delphi, dan schrijf je toch zelf een MessageDlg die zich wel netjes in die ModalFormStack plaatst?

Edit:

Iets als:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
unit u_ModalFormStack;

interface

uses
  Windows, contnrs, u_BaseModalForm;

type
  TModalFormStack = class
  protected
    FModalFormStack: TStack;
  public
    constructor Create;
    destructor Destroy; override;
    procedure PushModalForm(modalForm: TBaseModalForm);
    procedure PopModalForm(modalForm: TBaseModalForm);
    procedure CloseAllModalForms;
  end;

function ModalFormStack: TModalFormStack;

implementation

var
  myModalFormStack: TModalFormStack;

function ModalFormStack: TModalFormStack;
begin
  if not Assigned(myModalFormStack) then begin
    myModalFormStack := TModalFormStack.Create;
  end;

  Result := myModalFormStack;
end;

{ TModalFormStack }

constructor TModalFormStack.Create;
begin
  FModalFormStack := TStack.Create;
end;

destructor TModalFormStack.Destroy;
begin
  FModalFormStack.Free;

  inherited;
end;

procedure TModalFormStack.PopModalForm(modalForm: TBaseModalForm);
begin
  FModalFormStack.Pop;
end;

procedure TModalFormStack.PushModalForm(modalForm: TBaseModalForm);
begin
  FModalFormStack.Push(modalForm);
end;

procedure TModalFormStack.CloseAllModalForms;
var
  ActiveModalForm: TBaseModalForm;
begin
  while FModalFormStack.Count > 0 do begin
    ActiveModalForm := TBaseModalForm(FModalFormStack.Peek);

    repeat
      SendMessage(ActiveModalForm.Handle, MSG_CLOSEMODALFORM, 0, 0);
      Sleep(10);
    until ((FModalFormStack.Count = 0) or (FModalFormStack.Peek <> ActiveModalForm));
  end;
end;

initialization
  myModalFormStack := nil;
finalization
  if Assigned(myModalFormStack) then begin
    myModalFormStack.Free;
  end;
end.


Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
unit u_BaseModalForm;

interface

uses
  Classes, Controls, Forms, Messages;

const
  MSG_CLOSEMODALFORM = WM_USER + 1000;

type
  TBaseModalForm = class(TForm)
  private
    procedure OnCloseModalForm(var Msg: TMessage); message MSG_CLOSEMODALFORM;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

implementation

uses
  u_ModalFormStack;

{ TBaseModalForm }

constructor TBaseModalForm.Create(AOwner: TComponent);
begin
  inherited;

  ModalFormStack.PushModalForm(Self);
end;

destructor TBaseModalForm.Destroy;
begin
  ModalFormStack.PopModalForm(Self);

  inherited;
end;

procedure TBaseModalForm.OnCloseModalForm(var Msg: TMessage);
begin
  ModalResult := mrAbort;
end;

end.


En dan verander je de code uit de TS post naar iets als:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
procedure TfrmMain.LogoffUser; 
var 
  i: integer; 
begin 
  // logout inactive user 
  UserInfo.UserName := ''; 
  // close modal forms
  ModalFormStack.CloseAllModalForms;
  // close forms 
  CloseChildForms(Self); 
  for i := 0 to MdiChildCount - 1 do begin 
    CloseChildForms(MdiChildren[i]); 
    MdiChildren[i].OnCloseQuery := nil; 
    MdiChildren[i].Close; 
  end; 
  QuickLogout(miQuickLogout); 
end;


Voordeel op deze manier is dat elk modal form zichzelf netjes sluit, en dat dus ook de aanroepende code netjes wordt afgehandeld. Pas als het form echt vrij wordt gegeven... dan wordt het uit de form stack verwijderd, en zal de CloseAllModalForms pas het volgende modal form gaan sluiten.
Maar bij een bestaande (en vrij grote) applicatie is dat een flinke ingreep, en wanneer je je klanten niet als betatester wilt gebruiken duurt 't best lang voor je een uitrol kunt doen.
De wijziging valt toch op zich wel mee? En dat is toch zo bij elke wijziging die je doet? Dat test je toch eerst lokaal voordat je het op de klanten gaat uittesten?

Anyways... succes er mee :)

[ Voor 78% gewijzigd door IWriteCode op 16-05-2008 11:24 . Reden: ff klein compile vautje er uit gehaald... ]

Less = more


  • pedorus
  • Registratie: Januari 2008
  • Niet online
Het origineel werkt niet goed met ShowMessage/MessageDlg. Na wat puzzelen kwam ik hierop:
Delphi:
1
2
3
4
5
6
7
8
9
10
for i:=0 to Screen.FormCount-1 do begin
  if (Screen.Forms[i]<>self) then
    with Screen.Forms[i] do begin
      OnCloseQuery:=nil;
      if FormStyle <> fsMDIChild then
        OnClose:=nil;
      ModalResult:=mrAbort;
      Close;
    end
end;

Dit geeft een exception als er daarna HandleMessage wordt gedaan voordat de Timer is afgehandeld, bijvoorbeeld bij ShowModal. Ook niet-Delphi modal forms zoals MessageBox geven een exception.

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


  • IWriteCode
  • Registratie: Juli 2000
  • Laatst online: 18-09 13:34

IWriteCode

Less = more

Er zit een probleempje in bovenstaande code van mijzelf... dit omdat er allemaal gewerkt wordt vanuit de main thread... en die locked dan zichzelf... dit is op te lossen door aan de u_ModalFormStack een Windows Handle toe te voegen, dan kan de stack zichzelf berichten posten. Deze worden dan afgehandeld als de thread 'klaar' is. Heb nu een test programmaatje... en dat werkt prima.

Enige probleem wat ik nu heb, en dat is op dezelfde manier op te lossen, is dat er in de main thread gewacht moet worden tot de ModalFormStack.CloseAllModalForms klaar is. Dit kan dus op een identieke wijze gebeuren.

De stack code is nu:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
unit u_ModalFormStack;

interface

uses
  Windows, contnrs, u_BaseModalForm, Forms, Messages, Controls, Classes, syncobjs;

const
  MSG_CLOSEMODALFORMS = WM_USER + 1001;

type
  TModalFormStack = class(TObject)
  private
    FHandle: HWND;
    procedure CloseModalForms;
  protected
    FModalFormStack: TStack;
    procedure WndMethod(var Msg: TMessage); virtual;
  public
    constructor Create;
    destructor Destroy; override;

    function ModalFormsRemaining: boolean;
    procedure PushModalForm(modalForm: TBaseModalForm);
    procedure PopModalForm(modalForm: TBaseModalForm);
    procedure CloseAllModalForms;
  end;

function ModalFormStack: TModalFormStack;

implementation

var
  myModalFormStack: TModalFormStack;

function ModalFormStack: TModalFormStack;
begin
  if not Assigned(myModalFormStack) then begin
    myModalFormStack := TModalFormStack.Create;
  end;

  Result := myModalFormStack;
end;

{ TModalFormStack }

constructor TModalFormStack.Create;
begin

  FHandle := AllocateHWnd(WndMethod);
  FModalFormStack := TStack.Create;
end;

destructor TModalFormStack.Destroy;
begin
  FModalFormStack.Free;
  DeallocateHWnd(FHandle);

  inherited;
end;

procedure TModalFormStack.PopModalForm(modalForm: TBaseModalForm);
begin
  FModalFormStack.Pop;
end;

procedure TModalFormStack.PushModalForm(modalForm: TBaseModalForm);
begin
  FModalFormStack.Push(modalForm);
end;

function TModalFormStack.ModalFormsRemaining: boolean;
begin
  Result := (FModalFormStack.Count <> 0);
end;

procedure TModalFormStack.CloseAllModalForms;
begin
  PostMessage(FHandle, MSG_CLOSEMODALFORMS, 0, 0);
end;

procedure TModalFormStack.CloseModalForms;
var
  ActiveModalForm: TBaseModalForm;
begin
  if ModalFormsRemaining then begin
    ActiveModalForm := TBaseModalForm(FModalFormStack.Peek);

    PostMessage(ActiveModalForm.Handle, MSG_CLOSEMODALFORM, 0, 0);
    PostMessage(FHandle, MSG_CLOSEMODALFORMS, 0, 0);
  end;
end;

procedure TModalFormStack.WndMethod(var Msg: TMessage);
var
  Handled: Boolean;
begin
  Handled := True;
  case Msg.Msg of
    MSG_CLOSEMODALFORMS: begin
        CloseModalForms;
      end;
  else
    Handled := False;
  end;

  if Handled then begin
    Msg.Result := 0
  end
  else begin
    Msg.Result := DefWindowProc(FHandle, Msg.Msg, Msg.WParam, Msg.LParam);
  end;
end;

initialization
  myModalFormStack := nil;
finalization
  if Assigned(myModalFormStack) then begin
    myModalFormStack.Free;
  end;
end.


En de mainform code, met eerder gesuggereerde sturen van windows message naar zichzelf:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
const
  MSG_CLOSEALLFORMS = WM_USER + 1002;

type
  TForm1 = class(TForm)
  private
    procedure OnCloseAllForms(var Msg: TMessage); message MSG_CLOSEALLFORMS;
    procedure CloseChildForms(AForm: TForm);
    procedure LogoffUser;
    { Private declarations }
  public
    { Public declarations }
  end;

procedure TForm1.CloseChildForms(AForm: TForm);
var
  i: integer;
begin
  for i := 0 to AForm.ComponentCount - 1 do begin
    if (AForm.Components[i] is TForm) and (TForm(AForm.Components[i]).FormStyle <> fsMDIChild) then begin
      CloseChildForms(TForm(AForm.Components[i]));
      TForm(AForm.Components[i]).OnCloseQuery := nil;
      TForm(AForm.Components[i]).OnClose := nil;
      TForm(AForm.Components[i]).ModalResult := mrAbort;
      TForm(AForm.Components[i]).Close;
    end;
  end;
end;

procedure TForm1.LogoffUser;
begin
  // logout inactive user
  UserInfo.UserName := '';

  PostMessage(Self.Handle, MSG_CLOSEALLFORMS, 0, 0);
end;

procedure TForm1.OnCloseAllForms(var Msg: TMessage);
var
  i: integer;
begin
  if (ModalFormStack.ModalFormsRemaining) then begin
    ModalFormStack.CloseAllModalForms;
    PostMessage(Self.Handle, MSG_CLOSEALLFORMS, 0, 0);
  end else begin
    // close forms
    CloseChildForms(Self);
    for i := 0 to MdiChildCount - 1 do begin
      CloseChildForms(MdiChildren[i]);
      MdiChildren[i].OnCloseQuery := nil;
      MdiChildren[i].Close;
    end;
    QuickLogout(miQuickLogout);
  end;
end;


Code getest, en lijkt prima te werken :)

[ Voor 20% gewijzigd door IWriteCode op 16-05-2008 14:06 ]

Less = more


  • pedorus
  • Registratie: Januari 2008
  • Niet online
Jamal schreef op vrijdag 16 mei 2008 @ 13:58:
Code getest, en lijkt prima te werken :)
Werken zal het wel, maar het is wel veel code/werk. Waarom hou je apart bij welke ModalForms er zijn? Delphi houdt dat al voor je bij:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
function ModalFormsRemaining(): boolean;
var i: Integer;
begin
  Result:=false;
  for i:=0 to Screen.FormCount-1 do begin
    if fsModal in Screen.Forms[i].FormState then begin
      Result:=true;
      exit;
    end;
  end;
end;

Je kan ze allemaal in een keer sluiten, en dan een PostMessage naar MainForm doen als je een nieuwe wil openen. Theoretisch kan het voorkomen dat inmiddels weer een nieuwe ModalForm geopend is met een muisklik vanuit MainForm. (Oplossing: ModalFormsRemaining() nogmaals checken en herhalen, maar kans is zeer zeer klein, hieronder al opgelost).

Voor de duidelijkheid met code:

Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
procedure TForm1.LogoffUser;
begin
  // logout inactive user to prevent bad MDIChild OnClose action:
  UserInfo.UserName := ''; 

  OnCloseAllForms(nil);
end;

procedure TForm1.OnCloseAllForms(var Msg: TMessage);
var
  i: integer;
begin
  for i:=0 to Screen.FormCount-1 do begin
    if (Screen.Forms[i]<>self) then
      with Screen.Forms[i] do begin
        OnCloseQuery:=nil;
        if FormStyle <> fsMDIChild then
          OnClose:=nil;
        ModalResult:=mrAbort;
        Close;
      end
  end;
  //Check if we just closed some ModalForms. 
  //If we did, we cannot immediately open a new one.
  if ModalFormsRemaining() then 
    PostMessage(Self.Handle, MSG_CLOSEALLFORMS, 0, 0)
  else
    QuickLogout(miQuickLogout);
end;

Ongetest. Eventueel kan dit zo'n 4 regels langer en is die ModalFormsRemaining() niet meer nodig.

[ Voor 5% gewijzigd door pedorus op 16-05-2008 15:12 ]

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


  • IWriteCode
  • Registratie: Juli 2000
  • Laatst online: 18-09 13:34

IWriteCode

Less = more

Het probleem met de modal forms is, dat deze in de main thread blijven wachten tot ze klaar zijn... en dat de code na de showmodal nog wordt uitgevoerd. Daarom wil je de modal forms zichzelf laten sluiten in de omgekeerde volgorde dat ze gecreeerd zijn, vandaar die stack.

Door het op mijn manier te doen, hou je netjes de flow zoals die al eerder in het programma gedaan wordt, er wordt voor het programma in alle modal forms op de cancel gedrukt.

Less = more


  • pedorus
  • Registratie: Januari 2008
  • Niet online
Als je perse in de juiste volgorde wil sluiten, dan kan dat want Delphi houdt ook de bovenste ModalForm voor je bij; dat is nml Screen.Active(Custom)Form. Ik bedenk me dan ook dat die voorbeeldfunctie nog veel makkelijker kan:
Delphi:
1
2
3
4
function ModalFormsRemaining(): boolean;
begin
  Result:=fsModal in Screen.ActiveCustomForm.FormState;
end;

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


  • LordLarry
  • Registratie: Juli 2001
  • Niet online

LordLarry

Aut disce aut discede

Jamal schreef op vrijdag 16 mei 2008 @ 13:58:
Er zit een probleempje in bovenstaande code van mijzelf... dit omdat er allemaal gewerkt wordt vanuit de main thread... en die locked dan zichzelf... dit is op te lossen door aan de u_ModalFormStack een Windows Handle toe te voegen, dan kan de stack zichzelf berichten posten. Deze worden dan afgehandeld als de thread 'klaar' is. Heb nu een test programmaatje... en dat werkt prima.
Hmm, dat zei ik toch al? :)

We adore chaos because we like to restore order - M.C. Escher


  • IWriteCode
  • Registratie: Juli 2000
  • Laatst online: 18-09 13:34

IWriteCode

Less = more

Yup. Maar goed. Het probleem van TS is dus op zich redelijk eenvoudig op te lossen. :)

Less = more


Verwijderd

Topicstarter
Sorry voor de late reactie, maar ik was vandaag met iets heel anders bezig (een C# webservice).
pedorus' aanpak klinkt 't mooist, maar ik ben bang dat ik dan tegen dezelfde problemen op ga lopen. Maar dat ga ik komende week testen.
Jamal, jouw oplossing is prachtig wanneer je een framework opzet, maar wanneer je 't in een bestaande applicatie met 900.000+ regels code en zo'n 160 modal forms moet inbouwen is 't niet meer zo "redelijk eenvoudig op te lossen"... :)

Maar bedankt voor het meedenken, en ik laat jullie weten waar ik op uitgekomen ben. (eerst de pedorus versie, en daarna die van Jamal, maar da's een rotklus...)

Edit: pedorus, OnCloseAllForms is m.i. een vreemde naamgeving. On.. duidt op een event handler, terwijl 't hier een directe aanroep is. CloseAllForms of DoCloseAllForms lijkt me hier een betere naam. Maar da's muggenziften in de marge. ;)

[ Voor 16% gewijzigd door Verwijderd op 17-05-2008 02:25 ]

Pagina: 1