Question from Tapuz .Net forum: Nested DataBound Controls
שאלה:
יש לנו GridView שנטען מ-DataTable כלשהו ובתוכו יש TemplateField עם DropDownList.
הרשימה של ה- DropDownList אמורה להתמלא מ Datatable אחר.
איך אני טוען את ה DropDownList לרשימה נפרדת משלו ?
תשובה:
שאלה מצויינת שבאמת מראה שהגעת לעומק הקורה בעבודה עם ASP.Net.
נבין קודם את הבעיה ואז נתמקד בפתרון.
נתחיל בליצור דף ASP.Net חדש.
לדף נוסיף GridView.
ב-GridView שלנו נרצה להציג תמונות של בקבוקי וויסקי, השם שלהם ונאפשר למשתמש לבחור איפה נחזיק את הבקבוק וויסקי שלנו.
נתחיל בליצור מחלקה שתייצג את הבקבוקי וויסקי שלנו. ניצור קובץ חדש בשם WhiskeyBottle.cs.
(יש כמובן לציין שבדוגמה בעולם האמיתי, היינו יוצרים את המחלקה ב-Class Library מחוץ לפרוייקט ה-Web שלנו)
קיבלנו את הקובץ הבא:
נוסיף למחלקה שני נתונים: שם בקבוק וויסקי, כתובת לתמונה שלו ואיפה הוא מוחזק כרגע.
public class WhiskeyBottle
{
public WhiskeyBottle(string Name, string PictureUrl, StorageLocation Storage)
{
this.Name = Name;
this.PictureUrl = PictureUrl;
this.Storage = Storage;
}
private string _name = string.Empty;
public string Name
{
get { return _name; }
set { _name = value; }
}
private string _pictureUrl = string.Empty;
public string PictureUrl
{
get { return _pictureUrl; }
set { _pictureUrl = value; }
}
private StorageLocation _storage = new StorageLocation();
public StorageLocation Storage
{
get { return _storage; }
set { _storage = value; }
}
}
אין פה הרבה - שלושה מאפיינים (גם באנגלית: Properties) וקונסטרקטור.
ניצור גם קובץ SotrageLocation.cs שיחזיק את מחלקת ה-StorageLocation.
public class StorageLocation
{
public StorageLocation()
{
}
public StorageLocation(string Name)
{
this.Name = Name;
}
private string _name = string.Empty;
public string Name
{
get { return _name; }
set { _name = value; }
}
public override string ToString()
{
return Name;
}
}
גם פה אין הרבה, מאפיין אחד, קונסטרקטורים ודרסנו את ToString שנצטרך אותו בהמשך.
נבנה ב-Code-Behind של הדף שלנו קצת קוד שייצר לנו נתונים לדוגמה (אשר לכאורה מגיעים מהמסד נתונים).
public List<StorageLocation> GetStorageLocations()
{
List<StorageLocation> ReturnValues = new List<StorageLocation>();
ReturnValues.Add(new StorageLocation("Bar"));
ReturnValues.Add(new StorageLocation("Closet"));
ReturnValues.Add(new StorageLocation("On the top shelf"));
ReturnValues.Add(new StorageLocation("On the low shelf"));
return ReturnValues;
}
בנינו רשימה של StorageLocation וזאתי תהיה ה-DataSource ל-DropDownList שלנו.
public List<WhiskeyBottle> GetWhiskeyBottle()
{
List<WhiskeyBottle> ReturnValues = new List<WhiskeyBottle>();
ReturnValues.Add(new WhiskeyBottle("Red Label", "~/bottles/RedLabel.jpg", GetStorageLocations()[0]));
ReturnValues.Add(new WhiskeyBottle("Black Label", "~/bottles/BlackLabel.jpg", GetStorageLocations()[1]));
ReturnValues.Add(new WhiskeyBottle("Green Label", "~/bottles/GreenLabel.jpg", GetStorageLocations()[1]));
ReturnValues.Add(new WhiskeyBottle("Gold Label", "~/bottles/GoldLabel.jpg", GetStorageLocations()[2]));
ReturnValues.Add(new WhiskeyBottle("Blue Label", "~/bottles/BlueLabel.jpg",GetStorageLocations()[3] ));
return ReturnValues;
}
כאן בנינו רשימה של בקבוקי וויסקי.
ניגש בחזרה ל-GridView שלנו ונרצה להוסיף טור טקסטואלי אחד לשם, טור טקסטואלי (ביינתים) למקום אחסון וטור תמונה לבקבוק הוויסקי.
נוסיף את שם הבקבוק.
את המיקום שלו.
ואת התמונה שלו.
וזה הקוד ASP.Net שקיבלנו עד כה.
<asp:GridView ID="grdBottles" runat="server" AutoGenerateColumns="False">
<Columns>
<asp:BoundField DataField="Name" HeaderText="Bottle Name"
SortExpression="Name" />
<asp:BoundField DataField="Storage" HeaderText="Location"
SortExpression="Storage" />
<asp:ImageField DataImageUrlField="PictureURL" DataImageUrlFormatString="{0}"
HeaderText="Picture">
</asp:ImageField>
</Columns>
</asp:GridView>
(שימו לב בבקשה שהוספתי AutoGenerateColumns=False)
נוסיף שתי שורות קוד אחרונות ונריץ את הדוגמה.
יש לנו מקור מידע, יש לנו טבלה, עכשיו רק צריך לחבר ביניהם.
protected void Page_Load(object sender, EventArgs e)
{
grdBottles.DataSource = GetWhiskeyBottle();
grdBottles.DataBind();
}
(זוכרים GetWhiskeyBottle מחזיר רשימת בקבוקי וויסקי ואנחנו כתבנו אותה)
כאשר נריץ נראה את הדף הבא.
עכשיו נגיע סוף-סוף לבעיה - איך עושים DataBinding ל-DropDownList בתוך GridView.
במקום ה-BoundField שמציג טקסט נרצה להוסיף DropDownList עם רשימת ערכים של StorageLocation.
<asp:BoundField DataField="Storage" HeaderText="Location"
SortExpression="Storage" />
יהפוך ל:
<asp:TemplateField HeaderText="Location" >
<ItemTemplate>
<asp:DropDownList ID="ddlLocations" runat="server"
DataSource=<%# GetStorageLocations() %>
DataTextField="Name" />
</ItemTemplate>
</asp:TemplateField>
בואו נראה את זה בהרצה.
אז מה עשינו כאן? השתמשנו ב-Late Data Bound Expression ברמת הדף כדי להכניס נתונים לתוך ה-DropDownList.
אבל אפשר לראות משהו מעבר לזה, הנתון שכרגע נבחר ב-DropDownList הוא לא הנתון ברמת המחלקה.
אז נקבע שני מאפיינים ל-DropDownList:
1. לקחת את ה-Value של כל פריט ממהמאפיין Name.
2. לקבוע SelectedValue כ-WhiskeyBottle.Storage.Name.
נשתמש ב-Eval שקשור ל-DataBinding של ההורה כדי להשיג את סעיף 2.
כרגע יש לנו את הקוד הבא ל-DropDownList.
<asp:DropDownList ID="ddlLocations" runat="server"
DataSource=<%# GetStorageLocations() %>
DataTextField="Name" />
והוא יהפוך ל:
<asp:DropDownList ID="ddlLocations" runat="server"
DataSource=<%# GetStorageLocations() %>
DataTextField="Name"
DataValueField="Name"
SelectedValue=<%# Eval("Storage.Name") %>/>
נראה איך זה נראה בתצוגה.
בואו נעשה סדר בבלאגן איך עשינו את זה.
בשלב א' הוספנו GridView ואמרנו שהיא משתמשת ב-DataSource ברמת הדף שנקבע ב-Code Behind.
בשלב ב' הוספנו DropDownList שיש לה DataSource ברמת הדף ונקבע בתוך הדף ASP.Net עצמו.
בשלב ג' דאגנו שה-DropDownList שלנו תביט על הערך שה-GridView כרגע קבע ל-DataBinding על השורה.
זאת באמצעות ביטוי ה-Eval שקשור לקונטקסט הנוכחי שעובר DataBinding.
שימו לב ש-Eval הלך לרמת הקונסטקסט הנוכחי, וביטוי Data Bound אחר הלך ישר לרמת הדף להוציא נתונים.
נעשה צעד אחד נוסף ואחרון ונעבור מ-String Based Reference למאפיין ל-Strongly Typed Reference.
<asp:DropDownList ID="ddlLocations" runat="server"
DataSource=<%# GetStorageLocations() %>
DataTextField="Name"
DataValueField="Name"
SelectedValue=<%# Eval("Storage.Name") %>/>
יהפוך ל:
<asp:DropDownList ID="DropDownList1" runat="server"
DataSource=<%# GetStorageLocations() %>
DataTextField="Name"
DataValueField="Name"
SelectedValue=<%# ((WebTesting_VS2008.WhiskeyBottle)Container.DataItem).Storage.Name %>/>
וזאת כדי שנקבל Intellisense בכתיבת הביטוי ושגיאות קומפילציה למקרה שהמאפיין לא קיים.
(Eval סה"כ ניגש ל-Container.DataItem ומבצע Reflection עליו לפי מחרוזת, אז אנחנו פשוט ישר ניגשנו למאפיין)
עוד על Late Data Bound Expressions ניתן לקרוא במאמר שלי משנת 2005:
Late Bound Data Expressions - הכוח שמאחורי הרעיון
את הקוד שעבדנו עליו ניתן להוריד - כאן.
קישור: http://www.tapuz.co.il/tapuzforum/main/Viewmsg.asp?forum=831&msgid=111887553