2010-05-18 162 views
6

這個問題最初是關於讓雙向綁定工作,但由於缺乏具體的答案和其他方面的進展,我一直在更新它 - 您可以查看編輯歷史記錄,但我認爲這更清楚。自定義模板化asp.net控件的雙向數據綁定

下面的代碼清單允許單個對象是模板化控件的雙向數據綁定。我想以最簡單的方式擴展此示例,以允許嵌套啓用類似雙向數據綁定的模板化控件,以實現最根本對象的複雜類型屬性。例如,SampleFormData有一個屬性List<string> Items。我希望能夠綁定到最根本模板(來自此代碼清單)的此列表,並且可以將字符串數據顯示在可編輯的文本框列表中,也許可以使用插入,刪除,重新綁定輸入的命令 - 改變(回到綁定對象的List屬性)。此外,如果這是一個複雜類型的列表(SampleFormChildData,而不是字符串),則可以在列表中使用新的嵌入式SampleSpecificEntryForm,該列表綁定到每個列表項目,如中繼器。如果作者如此選擇的話,那麼等到葉子簡單的屬性。 ui字段不需要自動生成,只能用於綁定。

注意:List<string>的情況很特殊,因爲即使內置綁定也無法直接將字符串作爲DataItem處理 - 直接綁定到字符串,因爲我們列表中的項目不是必需項,而是非常有價值。

這與FormView不同,因爲它不是爲了期望綁定到某個項目列表,而是建立爲僅保留在視圖狀態或任何地方的單個項目。與FormView不同,它只有一個類似於FormView的EditTemplate的默認模板。同樣,綁定到類似集合的屬性也只有一個視圖 - 編輯。沒有選擇該行,然後進行編輯。一切都是可編輯的。目的是使雙向綁定表單更容易構建。

對我來說似乎應該有兩種綁定。 SingleEntityBindingCollectionBindingSingleEntityBinding將單個對象實例作爲數據源(如SampleSpecificEntryForm原型),而CollectionBinding可能會綁定到其父SingleEntityBinding,其屬性爲DataSourceID="EntryForm1" DataMember="Items",如下面代碼示例DataList1所示。任一種類型的嵌套都應該得到支持。對錶單作者負責列表操作,例如插入/更改/刪除類型操作,以支持對象的數據;然而,這種機制實施起來相對簡單。

這裏有一些代碼,希望它有助於某人。 200點是在那裏爲實現這一佈局的目標,最好的建議...

using System.ComponentModel; 
using System.Collections.Specialized; 
using System.Collections.Generic; 

namespace System.Web.UI.WebControls.Special 
{ 
    [Serializable] 
    public class SampleFormData 
    { 
     public string SampleString { get; set; } 
     public int SampleInt { get; set; } 
     public List<string> Items { get; set; } 

     public SampleFormData() 
     { 
      SampleString = "Sample String Data"; 
      SampleInt = 5; 
      Items = new List<string>(); 
     } 
    } 

    [ToolboxItem(false)] 
    public class SampleSpecificFormDataContainer : WebControl, INamingContainer, IDataItemContainer 
    { 
     SampleSpecificEntryForm entryForm; 

     internal SampleSpecificEntryForm EntryForm 
     { 
      get { return entryForm; } 
     } 

     [Bindable(true), Category("Data")] 
     public string SampleString 
     { 
      get { return entryForm.FormData.SampleString; } 
      set { entryForm.FormData.SampleString = value; } 
     } 

     [Bindable(true), Category("Data")] 
     public int SampleInt 
     { 
      get { return entryForm.FormData.SampleInt; } 
      set { entryForm.FormData.SampleInt = value; } 
     } 

     [Bindable(true), Category("Data")] 
     public List<string> Items 
     { 
      get { return entryForm.FormData.Items; } 
      set { entryForm.FormData.Items = value; } 
     } 

     internal SampleSpecificFormDataContainer(SampleSpecificEntryForm entryForm) 
     { 
      this.entryForm = entryForm; 
     } 

     #region IDataItemContainer Members 
     public object DataItem { get { return entryForm.FormData; } } 

     public int DataItemIndex { get { return 0; } } 

     public int DisplayIndex { get { return 0; } } 
     #endregion 
    } 

    public class SampleSpecificEntryForm : DataBoundControl, INamingContainer, IDataSource 
    { 
     #region Template 
     private IBindableTemplate formTemplate = null; 

     [Browsable(false), DefaultValue(null), 
     TemplateContainer(typeof(SampleSpecificFormDataContainer), ComponentModel.BindingDirection.TwoWay), 
     PersistenceMode(PersistenceMode.InnerProperty)] 
     public virtual IBindableTemplate FormTemplate 
     { 
      get { return formTemplate; } 
      set { formTemplate = value; } 
     } 
     #endregion 

     public override ControlCollection Controls 
     { 
      get 
      { 
       EnsureChildControls(); 
       return base.Controls; 
      } 
     } 

     private SampleSpecificFormDataContainer formDataContainer = null; 

     [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
     public SampleSpecificFormDataContainer FormDataContainer 
     { 
      get 
      { 
       EnsureChildControls(); 
       return formDataContainer; 
      } 
     } 

     [Bindable(true), Browsable(false)] 
     public SampleFormData FormData 
     { 
      get 
      { 
       SampleFormData data = ViewState["FormData"] as SampleFormData; 

       if (data == null) 
       { 
        data = new SampleFormData(); 
        ViewState["FormData"] = data; 
       } 

       return data; 
      } 
     } 

     protected override void CreateChildControls() 
     { 
      if (!this.ChildControlsCreated) 
      { 
       this.ChildControlsCreated = true; 
       Controls.Clear(); 
       formDataContainer = new SampleSpecificFormDataContainer(this); 

       Controls.Add(formDataContainer); 
       FormTemplate.InstantiateIn(formDataContainer); 
      } 
     } 

     protected override void PerformDataBinding(Collections.IEnumerable ignore) 
     { 
      CreateChildControls(); 

      if (Page.IsPostBack) 
      { 
       //OrderedDictionary fields = new OrderedDictionary(); 

       //ExtractValuesFromBindableControls(fields, formDataContainer); // Don't know what this would be for 

       foreach (System.Collections.DictionaryEntry entry in formTemplate.ExtractValues(formDataContainer)) 
       { 
        if (((string)entry.Key).Equals("SampleString", StringComparison.Ordinal)) 
        { 
         FormData.SampleString = (string)entry.Value; 
        } 

        if (((string)entry.Key).Equals("SampleInt", StringComparison.Ordinal)) 
        { 
         int i; 
         if (int.TryParse((string)entry.Value, out i)) 
         { 
          FormData.SampleInt = i; 
         } 
        } 
       } 
      } 

      formDataContainer.DataBind(); 
     } 

     public SampleSpecificEntryForm() 
     { 
      this.PreRender += new EventHandler(SampleSpecificEntryForm_PreRender); 
     } 

     void SampleSpecificEntryForm_PreRender(object sender, EventArgs e) 
     { 
      SaveViewState(); 
     } 

     #region IDataSource Members 

     public event EventHandler DataSourceChanged; 

     public DataSourceView GetView(string viewName) 
     { 
      return new PropertyView(this, viewName); 
     } 

     public Collections.ICollection GetViewNames() 
     { 
      return new List<string>() { "SampleString", "SampleInt", "Items" }; 
     } 

     #endregion 
    } 

    // Not yet used ... 
    public class PropertyView : DataSourceView 
    { 
     SampleSpecificEntryForm owner; 
     string viewName; 

     protected override Collections.IEnumerable ExecuteSelect(DataSourceSelectArguments arguments) 
     { 
      if (viewName.Equals("SampleString", StringComparison.Ordinal)) 
      { 
       return new object[] { owner.FormData.SampleString }; 
      } 

      if (viewName.Equals("SampleInt", StringComparison.Ordinal)) 
      { 
       return new object[] { owner.FormData.SampleInt }; 
      } 

      if (viewName.Equals("Items", StringComparison.Ordinal)) 
      { 
       return new object[] { owner.FormData.Items }; 
      } 

      throw new InvalidOperationException(); 
     } 

     public PropertyView(SampleSpecificEntryForm owner, string viewName) 
      : base(owner, viewName) 
     { 
      this.owner = owner; 
      this.viewName = viewName; 
     } 
    } 
} 

隨着ASP.NET頁面如下:

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" 
    CodeBehind="Default2.aspx.cs" Inherits="EntryFormTest._Default2" EnableEventValidation="false" %> 

<%@ Register Assembly="EntryForm" Namespace="System.Web.UI.WebControls.Special" TagPrefix="cc1" %> 

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent"> 
</asp:Content> 
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent"> 
    <h2> 
     Welcome to ASP.NET! 
    </h2> 
     <cc1:SampleSpecificEntryForm ID="EntryForm1" runat="server"> 
    <FormTemplate> 
     <asp:TextBox ID="txtSampleString" runat="server" Text='<%# Bind("SampleString") %>'></asp:TextBox><br /> 
     <asp:TextBox ID="txtSampleInt" runat="server" Text='<%# Bind("SampleInt") %>'></asp:TextBox><br /> 
     <h3> 
      (<%# Container.SampleString %>, <%# Container.SampleInt %>) - aka - 
      (<%# DataBinder.Eval(Container, "SampleString")%>, <%# DataBinder.Eval(Container, "SampleInt")%>)</h3> 
     <br /> 
     <asp:Button ID="btnUpdate" runat="server" Text="Update" /><br /> 
     <br /> 
    </FormTemplate> 
</cc1:SampleSpecificEntryForm> 
</asp:Content> 

Default2.aspx.cs:

using System; 

namespace EntryFormTest 
{ 
    public partial class _Default2 : System.Web.UI.Page 
    { 
     protected void Page_Load(object sender, EventArgs e) 
     { 
      EntryForm1.DataBind(); 
     } 
    } 
} 

我實現了作爲的IDataSource很好,企圖能夠嵌套列表組件,像這樣(中):

<asp:DataList ID="DataList1" runat="server" DataSourceID="EntryForm1" DataMember="Items"> 
    <EditItemTemplate> 
     <asp:TextBox ID="TextBox3" runat="server" Text="<%# Bind(".") %>"></asp:TextBox> 
    </EditItemTemplate> 
    <FooterTemplate> 
     <asp:Button ID="Button2" runat="server" Text="Add" CommandName="Add" /> 
    </FooterTemplate> 
</asp:DataList> 

有關如何以層疊方式工作的任何想法都會很棒(例如,在Items list屬性上)。這裏面臨的挑戰之一是Bind()不能引用數據綁定對象本身(在這種情況下是一個字符串),而是引用該項的屬性 - 使綁定到列表很尷尬。

感謝您的幫助!一路上

實現IDataItemContainer


發現。我非常希望這會解決它,但不。沒有明顯的變化。 糟糕,在錯誤的類上實現它。現在它是Binding,但是這些值不會在回發時被綁定到綁定對象。嗯...

由於 this article建議,Page.GetDataItem()是異常的來源。如果頁面的_dataBindingContext爲null或空,則拋出此異常。文章沒有解釋這一點,但並沒有說明如何確保頁面的_dataBindingContext被填充。我會繼續尋找。

正如MSDN文檔所述,DataBoundControl應該實現PerformDataBinding而不是重寫DataBind()。我已經這樣做了,並且做出了雙向約束的工作。這段代碼是必要的還是應該使用內置的東西?

回答

0

您是否試過Databinder.Eval(Container.DataItem,...)語法?

另請參閱Bind()上的這篇文章。

PS。除非使用Viewstate來保存值,否則每次回發都需要Databind。

+0

是的,謝謝你的回覆。我嘗試了所有各種選擇。我用最新的信息更新了「沿途發現」的帖子。你以前是否在處理這個普遍問題? – 2010-05-21 16:19:08

+1

是的,我寫過數據綁定的自定義控件,儘管我一直在使用MVC。 PS。除非您使用Viewstate來保存值,否則需要在回發時使用Databind。 – kervin 2010-05-21 19:26:54

+0

好吧,切爾文。你贏了! – 2010-05-27 16:39:40