Έχω το ακόλουθο πρόβλημα.
Στην εφαρμογή μου έχω Φόρμες, αλλά και κλάσεις που κάνουν Facade κάποιες άλλες φόρμες που δεν ανήκουν στην εφαρμογή μου.
Εχω μια MainForm η οποία είναι MDI Control και ότι φόρμα ανοίγω θέλω να την έχω σαν MDI child.
Επίσης, έχω ένα Δέντρο σε ένα DockPanel της DevExpress όπου αναφέρεται κάθε φόρμα που αυτή τη στιγμή είναι ανοικτή.
Για να πραγματοποιήσω το παραπάνω σενάριο έχω φτοιάξει έναν provider που κρατάει όλες τις ανοικτές φόρμες σε ενα collection
και επιστρέφει Singleton Instances των φορμών αυτών. Η συνάρτηση που επιστρέφει τα instances είναι:
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
| /// <summary>
/// Checks if already exists an opened form of the specified type in the
/// collection of opened forms. If not creates and registers one.
/// </summary>
/// <typeparam name="T"> The type of Form to search for or create.</typeparam>
/// <param name="args"> Additional Form contstruction parameters. The order must
/// be the same with the order of appearance in each form's constructor.</param>
/// <returns> A singleton instance of a form of the requested type. </returns>
public static T GetFormInstance<T>(params object[] args) where T:IMdiChildForm
{
// If the form is not registered.
if (!m_openedFormsCollection.ContainsKey( typeof(T) ))
{
// Create it.
IMdiChildForm nForm = (IMdiChildForm)Activator.CreateInstance( typeof(T), args );
nForm.MdiParent = GmdMainForm.SingletonInstance;
// Register it.
m_openedFormsCollection.Add( nForm.GetType(), nForm );
if (MdiChildFormRegistered != null)
{
MdiChildFormRegistered( nForm );
}
// If the form is the first to open, or it opens in maximized environment
// then it is also Maximized.
nForm.WindowState = m_openedFormsCollection.Count == 1 ||
GmdMainForm.SingletonInstance.MdiChildren.Where( w => w.WindowState == FormWindowState.Maximized ).
Count() > 0
? FormWindowState.Maximized
: FormWindowState.Normal;
// Set the closed event to finalize the form instance.
nForm.OnMdiChildFormClosed += Form_FormClosed;
}
T result = (T)m_openedFormsCollection[typeof(T)];
return result;
} |
Τι IMdiChildForm είναι ένα interface που το υλοποιούν τόσο οι φόρμες μου όσο και τα Facades. Ο λόγος που το εισήγαγα είναι ακριβώς το ότι έχω κλάσεις
facdes, αλλιώς απλά θα έβαζα Form.
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
| /// <summary>
/// An interface used to expose necessery Properties of Forms and Form facades and
/// facilitate the access to all form related objects of this program.
/// </summary>
public interface IMdiChildForm
{
/// <summary>
/// Exposes the OnFormClose event of an MdiForm.
/// </summary>
event OpenedFormsProvider.OnMdiChildFormClosed OnMdiChildFormClosed;
/// <summary>
/// Exposes the MdiParent of an MdiForm.
/// </summary>
Form MdiParent { get; set; }
/// <summary>
/// Exposes the Window state of an MdiForm.
/// </summary>
FormWindowState WindowState { get; set; }
/// <summary>
/// Exposes the Window Title of an MdiForm.
/// </summary>
string Text { get; }
IntPtr Handle { get; }
/// <summary>
/// Exposes the function that brings an MdiForm to the Front of the visibility
/// Z-Buffer.
/// </summary>
void BringToFront();
void ShowMdiForm();
} |
Επιπλέον, για να μην επαναλαμβάνω τον ίδιο κώδικα έχω και μια Extension Function για να ανοίγω τις φόρμες:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| public static void ShowMdiChildForm(this IMdiChildForm referencedForm)
{
//(referencedForm as Form).Show();
referencedForm.ShowMdiForm();
// Each time a form is Shown, it is brought to the front of the Z-buffer.
referencedForm.BringToFront();
// If a form remains minimized after the BringToFront event, it means that
// the collection of minimized forms is visible, accesible by the mouse and
// thus the global state of forms in the MDI container is NOT maximized. We
// assume that the user normalized the front form, and to maintain the Normal
// state as the global one, we bring the window at Normal state.
if (referencedForm.WindowState == FormWindowState.Minimized)
{
referencedForm.WindowState = FormWindowState.Normal;
}
} |
Το παραπάνω σενάριο δουλεύει κανονικά εκτός από μια περίπτωση. Υπάρχη μια φόρμα που αφού την ανοίξω,
μόλις πάω να ανοίξω (το implementation της κλάσης για την συνάρτηση στην γραμμή 4 του 3ου snippet) μετά μια άλλη, τρώω
OutOfMemory Exception και μου λέει το σύστημα ότι δεν μπόρεσε να φτοιάξει handle για την φόρμα που
πάω να ανοίξω. Αυτό το έχω παρατηρήσει ΜΟΝΟ μετά από αυτή την φόρμα. Όλοι οι άλλοι συνδυασμοί δουλεύουν κανονικά.
To implementation της ShowMdiForm δεν είναι τίποτα άλλο από την Show() της εκάστοτε φόρμας.
Ψάχνοντας να βρώ τι παίζει παρατήρησα το εξής:
Στην γραμμή 16 του πρώτου snippet, μόλις δημιουργείται η φόρμα παίρνει ένα Handle.
Στην γραμμή 18 τώρα, μόλις αναθέσω το MDI parent, αυτός ο Handler χάνεται!
Αν βάλω Break Point εκεί:
α) Στις περιπτώσεις που δεν έχω πρόβλημα, παρατηρώ ότι απλά αλλάζει, και ούτε γάτα ούτε ζημιά.
β) Στην περίπτωση που η φόρμα είναι μετά από αυτήν που αναφέρω, τότε προπαθώντας από το watch (με το mouse) να δώ τον Handle, βλέπω
ότι το property ρίχνει OutOfMemory Exception.
- Αν πατήσω F5 τρώω και εγώ το Exception.
- Αν ξανακάνω watch τότε παρατηρώ ότι ο Handle πήρε τιμή και αν πατήσω F5 δουλεύει κανονικά.
Είναι σαν να γίνεται lazy ανάθεση του Handle από το MDIParent. Δοκίμασα να βάλω ένα "τζούφιο" while loop στην γραμμη 17, με συνθήκη Handle!=null,
και παραδόξως δεν παίρνω πια exception αλλά οι φόρμες μου ανοίγουν σε WindowState=Normal αντί για το τρέχω του MDI container.
Όσο για την "κακιά" φόρμα, δεν είναι Facade και η μόνη ιδιαιτερότητα που έχει είναι ότι δεν καλείται από την Main Form, και ότι χρησιμοποιεί ένα αντικείμενο το οποίο έχει φτοιαχτεί από
άλλο thread (BackGround worker).
Έχει κανείς καμοιά ιδέα?
Ευχαριστώ.