Παρατήρησα ότι το ActiveTabChanged του TabContainer δεν τρέχει όπως αναμένεται.
Θα περίμενε κανείς ότι μετά από κάθε PostBack και εφόσον εν τω μεταξύ έχει αλλάξει το ActiveTab στον client, θα τρέξει το ActiveTabChanged στον Server. Δυστυχώς αυτό δεν συμβαίνει και μάλιστα όταν συμβαίνει, γίνεται με τρόπο περίεργο.
Π.χ. αν έχεις στην σελίδα σου ένα TabContainer (με όλα τα παρελκόμενα TabPanels κλπ), ένα Button και ένα LinkButton, τότε ο TabContainer θα "ακούσει" μόνο το events που προέρχονται από το Button ενώ θα αγνοήσει τα PostBacks από το LinkButton. Δεν θα συμβεί όμως το ίδιο αν προσθέσεις και ένα ImageButton (οι PostBack event handlers του TabContainer, θα τρέξουν κανονικά σε κάθε PostBack που θα κάνει το ImageButton). Δοκίμασα το ίδιο και με ένα DropDownList με AutoPostBack=true. Τα ίδια με το LinkButton! Μόνο τα PostBacks των Button και ImageButton controls "φτάνουν" ως τον TabContainer. Όμως ένα PostBack είναι ένα PostBack! Δεν θα έπρεπε να συμπεριφέρεται διαφορετικά ανα περίπτωση ο TabContainer!!! Υπάρχουν και άλλοι πολλοί τρόποι να κάνει κανείς PostBack, τους οποίους προς το παρόν δεν έχω δοκιμάσει με το TabContainer. (Δεν ξέρω καν αν έχει και νόημα να μπω σε αυτή τη διαδικασία).
Όλα αυτά βέβαια συμβαίνουν ανεξάρτητα από το αν τα controls που προκαλούν το PostBack είναι μέσα σε κάποιο TabPanel ή όχι. Η συμπεριφορά αυτή (όπως εξάλου θα περίμενε κανείς) είναι επίσης ίδια, ανεξάρτητα από τον browser που χρησιμοποιεί κανείς. Το δοκίμασα με IE 7 και FireFox 2.
Τι τρέχει;
Τσέκαρα να δω αν ο TabContainer έχει AutoPostBack property για να ξεπεράσω προσωρινά το πρόβλημα με πλάγιο τρόπο. Δεν έχει. (Δεν θα είχε και πολύ νόημα εδώ που τα λέμε).
Στο CodePlex δεν βρήκα κάτι σχετικό που να δίνει μια λύση ή να εξηγεί το πρόβλημα. Το μόνο σχετικό link εκεί είναι το http://www.codeplex.com/AtlasControlToolkit/WorkItem/View.aspx?WorkItemId=7739.
Κοιτάζοντας το source στο TabContainer.cs παρατηρεί κανείς ότι το ActiveTabChanged καλείται στην RaisePostDataChangedEvent την οποία κάνει explicitly implement η base class ScriptControlBase από το interface IPostBackDataHandler (και override η TabContainer με την σειρά της). Κάνω copy/paste τα σχετικά code snippets από το TabContainer.cs.
protected override void RaisePostDataChangedEvent()
{
OnActiveTabChanged(EventArgs.Empty);
}
protected virtual void OnActiveTabChanged(EventArgs e)
{
EventHandler eh = Events[EventActiveTabChanged] as EventHandler;
if (eh != null)
{
eh(this, e);
}
}
private static readonly object EventActiveTabChanged = new object();
[Category("Behavior")]
public event EventHandler ActiveTabChanged
{
add { Events.AddHandler(EventActiveTabChanged, value); }
remove { Events.RemoveHandler(EventActiveTabChanged, value); }
}
H RaisePostDataChangedEvent θα έπρεπε να τρέχει εφόσον η LoadPostData επέστρεφε true. Η LoadPostData από το TabContainer.cs φαίνεται παρακάτω:
protected override bool LoadPostData(string postDataKey, NameValueCollection postCollection)
{
int tabIndex = ActiveTabIndex;
bool result = base.LoadPostData(postDataKey, postCollection);
if (tabIndex != ActiveTabIndex)
{
return true;
}
return result;
}
Και όπως είναι προφανές από το παραπάνω η LoadPostData προσπαθεί να βεβαιωθεί ότι όντως θα τρέξει ο event handler αν αλλάξει το ActiveTab στον client μεταξύ των PostBacks.
Όμως τελικά η LoadPostData καλώντας το base implementation (που σας παραθέτω παρακάτω) δεν βλέπει καμία αλλαγή στο ActiveTabIndex, παρά μόνο αν το PostBack έχει γίνει από ένα <input type=submit /> ή ένα <input type=image /> control, δηλαδή από ένα Button (με UseSubmitBehaviour=true) ή ένα ImageButton.
Κάνω copy/paste και το base implementation της LoadPostData από το ScriptControlBase.cs παρακάτω:
protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection)
{
if (SupportsClientState)
{
string clientState = postCollection[ClientStateFieldID];
if (!string.IsNullOrEmpty(clientState))
{
LoadClientState(clientState);
}
}
return false;
}
Η SupportsClientState επιστρέφει true για το TabContainer. H LoadClientState για το TabContainer έχει ως εξής:
protected override void LoadClientState(string clientState)
{
Dictionary<string, object> state = (Dictionary<string, object>)new JavaScriptSerializer().DeserializeObject(clientState);
if (state != null)
{
ActiveTabIndex = (int)state["ActiveTabIndex"];
object[] tabState = (object[])state["TabState"];
for (int i = 0; i < tabState.Length; i++)
{
Tabs[ i ].Enabled = (bool)tabState[ i ];
}
}
}
Τι ακριβώς τρέχει δεν έχω καταλάβει ακόμα. Πρέπει να είναι μπροστά μου και να μην το βλέπω! Ίσως κάτι δεν πάει καλά με τα client-side scripts στο Tabs.js (αν και δεν νομίζω ότί είναι εκεί το πρόβλημα). Τι διαφορετικό κάνει ένα Button από ένα LinkButton στο κατά το submit; Το LinkButton καλεί explicitly την __doPostBack. Η διαφορά είναι μια: τα Button και ImageButton controls κάνουν submit την φόρμα by design (ως HTML input controls), χωρίς να χρειάζεται να κληθεί η __doPostBack, σε αντίθεση με όλα τα άλλα controls που πρέπει να καλέσουν την __doPostBack (η οποία με την σειρά της καλεί την form.submit()). Κανονικά δεν θα έπρεπε άρα να υπάρχει καμία διαφορά άρα ανάμεσα στα δύο PostBacks (εκτός εάν η form.submit() δεν κάνει την ίδια δουλειά που κάνει ο browser όταν πατήσεις ένα submit input control!).
Που έχω καταλήξει; Το πρόβλημα εμφανίζεται στην LoadPostData. Εκεί το Framerwork περνάει μια παράμετρο τύπου NameValueCollection με όλα τα data της φόρμας. Εκεί υπάρχει και ένα ζευγάρι με το ClientState του TabContainer. To ClientState γινεται deserilize με έναν JavaScriptSerializer και διαβάζεται το περιεχομενό του από όπου προκύπτει η τρέχουσα τιμή για το ActiveTabIndex. Αυτό δεν είναι ενημερωμένο αν το PostBack γίνει με κλήση στην __doPostBack!!! Γιατί;;; Δεν έχω την παραμικρή ιδέα προς το παρόν!!!
Help me out here!!!!
rousso