Двусторонняя привязка данных настраиваемого шаблонного элемента управления asp.net - PullRequest
6 голосов
/ 18 мая 2010

Первоначально этот вопрос был о том, чтобы вообще получить двустороннюю привязку к работе, но из-за отсутствия конкретных ответов и иного прогресса в процессе, я обновлял его - вы можете проверить историю редактирования, но я решил, что лучше для наглядности.

Перечисленный ниже код позволяет одному объекту быть привязанным к шаблонному элементу управления. Я хотел бы расширить этот пример самым простым способом, чтобы разрешить вложение аналогичных шаблонных элементов управления с двусторонним связыванием данных для сложных типов свойств самого корневого объекта. Например, SampleFormData имеет свойство List<string> Items. Я хотел бы иметь возможность привязываться к этому списку в самом корневом шаблоне (из этого листинга кода) и либо отображать строковые данные в редактируемом списке текстовых полей, возможно, с помощью команд для вставки, удаления, повторного ввода -changes (обратно к свойству List связанного объекта). Кроме того, если бы это был список сложного типа (SampleFormChildData, а не строка), новый встроенный SampleSpecificEntryForm мог бы использоваться в списке, привязанном к каждому из элементов списка, как повторитель. И так далее, вплоть до простых свойств листа, если так решит автор. Поля пользовательского интерфейса не должны генерироваться автоматически, они просто доступны для привязки.

Примечание: случай List<string> является особенным, потому что даже встроенные привязки не могут обрабатывать строки как DataItem напрямую - привязка к строкам напрямую, поскольку элементы в нашем списке не являются обязательными, но, безусловно, ценными.

Это отличается от FormView, поскольку он не предназначен для привязки к одному из списка элементов, а только к одному элементу, который сохраняется в состоянии просмотра или где-либо еще. В отличие от FormView, он имеет только один шаблон по умолчанию, похожий на EditTemplate FormView. Аналогично, привязка к подобному коллекции свойству также может иметь только одно представление - редактировать. Там нет выбора строки, а затем редактирования. Все редактируется все время. Цель состоит в том, чтобы упростить создание двусторонне связанных форм.

Мне кажется, что должно быть два вида связывания. SingleEntityBinding и CollectionBinding. SingleEntityBinding принимает один экземпляр объекта в качестве источника данных (как прототип 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>

Любые мысли о том, как сделать эту работу каскадным способом, были бы замечательными (например, в свойстве «Список элементов»). Одна из проблем здесь заключается в том, что Bind () может ссылаться не на сам объект привязки данных (в данном случае на строку), а на свойство этого элемента, что делает связывание со списком неудобным.

Спасибо за любую помощь!


Открытия по пути

Реализован IDataItemContainer. Я очень надеялся, что это все исправит, но нет. Нет заметных изменений. Упс, реализовано не в том классе. Теперь это Binding, но значения не привязываются к связанному объекту при обратной передаче. Ммм ...

Поскольку эта статья предполагает, Page.GetDataItem () является источником исключения. Это исключение выдается, если _dataBindingContext страницы является нулевым или пустым. В статье это объясняется, но не говорится, как обеспечить заполнение _dataBindingContext страницы. Я продолжу искать.

Как сказано в документации MSDN, DataBoundControl должен реализовывать PerformDataBinding вместо переопределения DataBind (). Я сделал это и сделал двустороннюю обязательную работу. Нужен ли этот код или я должен использовать что-то встроенное?

1 Ответ

0 голосов
/ 21 мая 2010

Вы пробовали Databinder.Eval (Container.DataItem, ...) синтаксис?

Также смотрите эту статью на Bind () .

PS. Вам нужно привязывать данные при каждой обратной передаче, если вы не используете Viewstate для сохранения значений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...