File: UI\WebControls\ListControl.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <copyright file="ListControl.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
namespace System.Web.UI.WebControls {
 
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.Globalization;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.Adapters;
    using System.Web.Util;
    using System.Drawing;
    using System.Drawing.Design;
 
 
    /// <devdoc>
    ///    <para>An abstract base class. Defines the common
    ///       properties, methods, and events for all list-type controls.</para>
    /// </devdoc>
    [
    ControlValueProperty("SelectedValue"),
    DataBindingHandler("System.Web.UI.Design.WebControls.ListControlDataBindingHandler, " + AssemblyRef.SystemDesign),
    DefaultEvent("SelectedIndexChanged"),
    ParseChildren(true, "Items"),
    Designer("System.Web.UI.Design.WebControls.ListControlDesigner, " + AssemblyRef.SystemDesign)
    ]
    public abstract class ListControl : DataBoundControl, IEditableTextControl {
 
        private static readonly object EventSelectedIndexChanged = new object();
        private static readonly object EventTextChanged = new object();
 
        private ListItemCollection items;
        private int cachedSelectedIndex;
        private string cachedSelectedValue;
        private ArrayList cachedSelectedIndices;
        private bool _stateLoaded;
        private bool _asyncSelectPending;
 
 
        /// <devdoc>
        /// <para>Initializes a new instance of the <see cref='System.Web.UI.WebControls.ListControl'/> class.</para>
        /// </devdoc>
        public ListControl() {
            cachedSelectedIndex = -1;
        }
 
        /// <devdoc>
        ///    <para> Gets or sets a value
        ///       indicating whether databound items will be added to the list of staticly-declared
        ///       items in the list.</para>
        /// </devdoc>
        [
        DefaultValue(false),
        Themeable(false),
        WebCategory("Behavior"),
        WebSysDescription(SR.ListControl_AppendDataBoundItems),
        ]
        public virtual bool AppendDataBoundItems {
            get {
                object o = ViewState["AppendDataBoundItems"];
                if (o != null) {
                    return (bool)o;
                }
                return false;
            }
            set {
                ViewState["AppendDataBoundItems"] = value;
                if (Initialized) {
                    RequiresDataBinding = true;
                }
            }
        }
 
 
        /// <devdoc>
        ///    <para> Gets or sets a value
        ///       indicating whether an automatic postback to the server will occur whenever the
        ///       user changes the selection of the list.</para>
        /// </devdoc>
        [
        DefaultValue(false),
        WebCategory("Behavior"),
        WebSysDescription(SR.ListControl_AutoPostBack),
        Themeable(false),
        ]
        public virtual bool AutoPostBack {
            get {
                object b = ViewState["AutoPostBack"];
                return((b == null) ? false : (bool)b);
            }
            set {
                ViewState["AutoPostBack"] = value;
            }
        }
 
 
        [
        DefaultValue(false),
        Themeable(false),
        WebCategory("Behavior"),
        WebSysDescription(SR.AutoPostBackControl_CausesValidation)
        ]
        public virtual bool CausesValidation {
            get {
                object b = ViewState["CausesValidation"];
                return((b == null) ? false : (bool)b);
            }
            set {
                ViewState["CausesValidation"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para> Indicates the field of the
        ///       data source that provides the text content of the list items.</para>
        /// </devdoc>
        [
        DefaultValue(""),
        Themeable(false),
        WebCategory("Data"),
        WebSysDescription(SR.ListControl_DataTextField)
        ]
        public virtual string DataTextField {
            get {
                object s = ViewState["DataTextField"];
                return((s == null) ? String.Empty : (string)s);
            }
            set {
                ViewState["DataTextField"] = value;
                if (Initialized) {
                    RequiresDataBinding = true;
                }
            }
        }
 
 
        /// <devdoc>
        /// </devdoc>
        [
        DefaultValue(""),
        Themeable(false),
        WebCategory("Data"),
        WebSysDescription(SR.ListControl_DataTextFormatString)
        ]
        public virtual string DataTextFormatString {
            get {
                object s = ViewState["DataTextFormatString"];
                return ((s == null) ? String.Empty : (string)s);
            }
            set {
                ViewState["DataTextFormatString"] = value;
                if (Initialized) {
                    RequiresDataBinding = true;
                }
            }
        }
 
 
        /// <devdoc>
        ///    <para>Indicates the field of the data source that provides the value content of the
        ///       list items.</para>
        /// </devdoc>
        [
        DefaultValue(""),
        Themeable(false),
        WebCategory("Data"),
        WebSysDescription(SR.ListControl_DataValueField)
        ]
        public virtual string DataValueField {
            get {
                object s = ViewState["DataValueField"];
                return((s == null) ? String.Empty : (string)s);
            }
            set {
                ViewState["DataValueField"] = value;
                if (Initialized) {
                    RequiresDataBinding = true;
                }
            }
        }
 
        /// <devdoc>
        ///    <para>A protected property. Indicates if the ListControl supports multiple selections</para>
        /// </devdoc>
        internal virtual bool IsMultiSelectInternal {
            get  {
                return false;
            }
        }
 
 
 
        /// <devdoc>
        ///    <para>
        ///       Indicates the collection of items within the list.
        ///       This property
        ///       is read-only.</para>
        /// </devdoc>
        [
        WebCategory("Default"),
        DefaultValue(null),
        Editor("System.Web.UI.Design.WebControls.ListItemsCollectionEditor," + AssemblyRef.SystemDesign, typeof(UITypeEditor)),
        MergableProperty(false),
        WebSysDescription(SR.ListControl_Items),
        PersistenceMode(PersistenceMode.InnerDefaultProperty)
        ]
        public virtual ListItemCollection Items {
            get {
                if (items == null) {
                    items = new ListItemCollection();
                    if (IsTrackingViewState)
                        items.TrackViewState();
                }
                return items;
            }
        }
 
 
        /// <devdoc>
        ///    Determines whether the SelectedIndices must be stored in view state, to
        ///    optimize the size of the saved state.
        /// </devdoc>
        internal bool SaveSelectedIndicesViewState {
            get {
                // Must be saved when
                // 1. There is a registered event handler for SelectedIndexChanged or TextChanged.  
                //    For our controls, we know for sure that there is no event handler registered for 
                //    SelectedIndexChanged or TextChanged so we can short-circuit that check.
                // 2. Control is not enabled or visible, because the browser's post data will not include this control
                // 3. The instance is a derived instance, which might be overriding the OnSelectedIndexChanged method
                //    This is a bit hacky, since we have to cover all the four derived classes we have...
                // 4. AutoPostBack is true and Adapter doesn't support JavaScript
                //    For ListControls to behave the same on mobile devices
                //    that simulate AutoPostBack by rendering a command button, we need to save
                //    state.
                // 5. The control is paginated.
                // 6. The control contains items that are disabled.  The browser's post data will not
                //    include this data for disabled items, so we need to save those selected indices.
                //    
 
                if ((Events[EventSelectedIndexChanged] != null) ||
                    (Events[EventTextChanged] != null) ||
                    (IsEnabled == false) ||
                    (Visible == false) ||
                    (AutoPostBack == true && ((Page != null) && !Page.ClientSupportsJavaScript)) ) {
                    return true;
                }
 
                foreach (ListItem item in Items) {
                    if (item.Enabled == false) {
                        return true;
                    }
                }
 
                // Note that we added BulletedList that inherits ListControl in
                // Whidbey, but since it doesn't support selected index, we don't
                // need to check it here.
                Type t = this.GetType();
                if ((t == typeof(DropDownList)) ||
                    (t == typeof(ListBox)) ||
                    (t == typeof(CheckBoxList)) ||
                    (t == typeof(RadioButtonList))) {
                    return false;
                }
 
                return true;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Indicates the ordinal index of the first selected item within the
        ///       list.</para>
        /// </devdoc>
        [
        Bindable(true),
        Browsable(false),
        DefaultValue(0),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        Themeable(false),
        WebCategory("Behavior"),
        WebSysDescription(SR.WebControl_SelectedIndex),
        ]
        public virtual int SelectedIndex {
            get {
                for (int i=0; i < Items.Count; i++) {
                    if (Items[i].Selected)
                        return i;
                }
                return -1;
            }
            set {
                if (value < -1) {
                    if (Items.Count == 0) {
                        // VSW 540083: If there are no items, setting SelectedIndex < -1 is the same as setting it to -1.  Don't throw.
                        value = -1;
                    }
                    else {
                        throw new ArgumentOutOfRangeException("value", SR.GetString(SR.ListControl_SelectionOutOfRange, ID, "SelectedIndex"));
                    }
                }
 
                if ((Items.Count != 0 && value < Items.Count) || value == -1) {
                    ClearSelection();
                    if (value >= 0) {
                        Items[value].Selected = true;
                    }
                }
                else {
                    // if we're in a postback and our state is loaded but the selection doesn't exist in the list of items,
                    // throw saying we couldn't find the selected item.
                    if (_stateLoaded) {
                        throw new ArgumentOutOfRangeException("value", SR.GetString(SR.ListControl_SelectionOutOfRange, ID, "SelectedIndex"));
                    }
                }
                // always save the selectedindex
                // When we've databound, we'll have items from viewstate on the next postback.
                // If we don't cache the selected index and reset it after we databind again,
                // the selection goes away.  So we always have to save the selectedIndex for restore
                // after databind.
                cachedSelectedIndex = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para>A protected property. Gets an array of selected
        ///       indexes within the list. This property is read-only.</para>
        /// </devdoc>
        internal virtual ArrayList SelectedIndicesInternal {
            get {
                cachedSelectedIndices = new ArrayList(3);
                for (int i=0; i < Items.Count; i++) {
                    if (Items[i].Selected)  {
                        cachedSelectedIndices.Add(i);
                    }
                }
                return cachedSelectedIndices;
            }
        }
 
 
 
        /// <devdoc>
        ///    <para>Indicates the first selected item within the list.
        ///       This property is read-only.</para>
        /// </devdoc>
        [
        WebCategory("Behavior"),
        Browsable(false),
        DefaultValue(null),
        WebSysDescription(SR.ListControl_SelectedItem),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
        ]
        public virtual ListItem SelectedItem{
            get {
                int i = SelectedIndex;
                return(i < 0) ? null : Items[i];
            }
        }
 
 
 
        /// <devdoc>
        ///    <para>Indicates the value of the first selected item within the
        ///       list.</para>
        /// </devdoc>
        [
        Bindable(true, BindingDirection.TwoWay),
        Browsable(false),
        DefaultValue(""),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        Themeable(false),
        WebSysDescription(SR.ListControl_SelectedValue),
        WebCategory("Behavior"),
        ]
        public virtual string SelectedValue {
            get {
                int i = SelectedIndex;
                return (i < 0) ? String.Empty : Items[i].Value;
            }
            set {
                if (Items.Count != 0) {
                    // at design time, a binding on SelectedValue will be reset to the default value on OnComponentChanged
                    if (value == null || (DesignMode && value.Length == 0)) {
                        ClearSelection();
                        return;
                    }
 
                    ListItem selectItem = Items.FindByValue(value);
                    // if we're in a postback and our state is loaded or the page isn't a postback but all persistance is loaded
                    // but the selection doesn't exist in the list of items,
                    // throw saying we couldn't find the selected value.
                    bool loaded = Page != null && Page.IsPostBack && _stateLoaded;
 
                    if (loaded && selectItem == null) {
                        throw new ArgumentOutOfRangeException("value", SR.GetString(SR.ListControl_SelectionOutOfRange, ID, "SelectedValue"));
                    }
 
                    if (selectItem != null) {
                        ClearSelection();
                        selectItem.Selected = true;
                    }
                }
                // always save the selectedvalue
                // for later databinding in case we have viewstate items or static items
                cachedSelectedValue = value;
            }
        }
 
        [
        Browsable(false),
        Themeable(false),
        DefaultValue(""),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        WebSysDescription(SR.ListControl_Text),
        WebCategory("Behavior"),
        ]
        public virtual string Text {
            get {
                return SelectedValue;
            }
            set {
                SelectedValue = value;
            }
        }
 
 
        protected override HtmlTextWriterTag TagKey {
            get {
                return HtmlTextWriterTag.Select;
            }
        }
 
 
        [
        WebCategory("Behavior"),
        Themeable(false),
        DefaultValue(""),
        WebSysDescription(SR.PostBackControl_ValidationGroup)
        ]
        public virtual string ValidationGroup {
            get {
                string s = (string)ViewState["ValidationGroup"];
                return((s == null) ? string.Empty : s);
            }
            set {
                ViewState["ValidationGroup"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    Occurs when the list selection is changed upon server
        ///    postback.
        /// </devdoc>
        [
        WebCategory("Action"),
        WebSysDescription(SR.ListControl_OnSelectedIndexChanged)
        ]
        public event EventHandler SelectedIndexChanged {
            add {
                Events.AddHandler(EventSelectedIndexChanged, value);
            }
            remove {
                Events.RemoveHandler(EventSelectedIndexChanged, value);
            }
        }
 
 
        /// <devdoc>
        ///    <para>Occurs when the content of the text box is
        ///       changed upon server postback.</para>
        /// </devdoc>
        [
        WebCategory("Action"),
        WebSysDescription(SR.ListControl_TextChanged)
        ]
        public event EventHandler TextChanged {
            add {
                Events.AddHandler(EventTextChanged, value);
            }
            remove {
                Events.RemoveHandler(EventTextChanged, value);
            }
        }
 
 
        protected override void AddAttributesToRender(HtmlTextWriter writer) {
            // Make sure we are in a form tag with runat=server.
            if (Page != null) {
                Page.VerifyRenderingInServerForm(this);
            }
 
            if (IsMultiSelectInternal)  {
                writer.AddAttribute(HtmlTextWriterAttribute.Multiple, "multiple");
            }
 
            if (AutoPostBack && (Page != null) && Page.ClientSupportsJavaScript) {
                string onChange = null;
                if (HasAttributes) {
                    onChange = Attributes["onchange"];
                    if (onChange != null) {
                        onChange = Util.EnsureEndWithSemiColon(onChange);
                        Attributes.Remove("onchange");
                    }
                }
 
                PostBackOptions options = new PostBackOptions(this, String.Empty);
 
                // ASURT 98368
                // Need to merge the autopostback script with the user script
                if (CausesValidation) {
                    options.PerformValidation = true;
                    options.ValidationGroup = ValidationGroup;
                }
 
                if (Page.Form != null) {
                    options.AutoPostBack = true;
                }
 
                onChange = Util.MergeScript(onChange, Page.ClientScript.GetPostBackEventReference(options, true));
 
                writer.AddAttribute(HtmlTextWriterAttribute.Onchange, onChange);
                if (EnableLegacyRendering) {
                    writer.AddAttribute("language", "javascript", false);
                }
            }
 
            if (Enabled && !IsEnabled & SupportsDisabledAttribute) {
                // We need to do the cascade effect on the server, because the browser
                // only renders as disabled, but doesn't disable the functionality.
                writer.AddAttribute(HtmlTextWriterAttribute.Disabled, "disabled");
            }
 
            base.AddAttributesToRender(writer);
        }
 
 
        /// <devdoc>
        ///    <para> Clears out the list selection and sets the
        ///    <see cref='System.Web.UI.WebControls.ListItem.Selected'/> property
        ///       of all items to false.</para>
        /// </devdoc>
        public virtual void ClearSelection() {
            for (int i=0; i < Items.Count; i++)
                Items[i].Selected = false;
            // Don't clear cachedSelectedIndices here because some databound controls (such as SiteMapPath)
            // call databind on all child controls when restoring from viewstate.  We need to preserve the
            // cachedSelectedIndices and restore them again for the second databinding.
        }
 
 
        /// <internalonly/>
        /// <devdoc>
        ///    Load previously saved state.
        ///    Overridden to restore selection.
        /// </devdoc>
        protected override void LoadViewState(object savedState) {
            if (savedState != null) {
                Triplet stateTriplet = (Triplet)savedState;
                base.LoadViewState(stateTriplet.First);
 
                // restore state of items
                Items.LoadViewState(stateTriplet.Second);
 
                // restore selected indices
                ArrayList selectedIndices = stateTriplet.Third as ArrayList;
                if (selectedIndices != null) {
                    SelectInternal(selectedIndices);
                }
            }
            else {
                base.LoadViewState(null);
            }
 
            _stateLoaded = true;
        }
 
 
        private void OnDataSourceViewSelectCallback(IEnumerable data) {
            _asyncSelectPending = false;
            PerformDataBinding(data);
            PostPerformDataBindingAction();
        }
 
        protected override void OnDataBinding(EventArgs e) {
            base.OnDataBinding(e);
            DataSourceView view = GetData();
 
            // view could be null when a user implements his own GetData().
            if (null == view) {
                throw new InvalidOperationException(SR.GetString(SR.DataControl_ViewNotFound, ID));
            }
 
            // DevDiv 1036362: enable async model binding for ListControl
            bool useAsyncSelect = false;
            if (AppSettings.EnableAsyncModelBinding) {
                var modelDataView = view as ModelDataSourceView;
                useAsyncSelect = modelDataView != null && modelDataView.IsSelectMethodAsync;
            }
 
            if (useAsyncSelect) {
                _asyncSelectPending = true; // disable post data binding action until the callback is invoked
                view.Select(SelectArguments, OnDataSourceViewSelectCallback);
            }
            else {
                IEnumerable data = view.ExecuteSelect(DataSourceSelectArguments.Empty);
                PerformDataBinding(data);
            }
        }
 
        internal void EnsureDataBoundInLoadPostData() {
            if (!SkipEnsureDataBoundInLoadPostData) {
                EnsureDataBound();
            }
        }
 
        internal bool SkipEnsureDataBoundInLoadPostData {
            get;
            set;
        }
 
 
        /// <internalonly/>
        protected internal override void OnPreRender(EventArgs e) {
            base.OnPreRender(e);
            if (Page != null && IsEnabled) {
                if (AutoPostBack) {
                    Page.RegisterPostBackScript();
                    Page.RegisterFocusScript();
 
                    // VSWhidbey 489577
                    if (CausesValidation && Page.GetValidators(ValidationGroup).Count > 0) {
                        Page.RegisterWebFormsScript();
                    }
                }
 
                if (SaveSelectedIndicesViewState == false) {
                    // Store a client-side array of enabled control, so we can re-enable them on
                    // postback (in case they are disabled client-side)
                    // Postback is needed when the SelectedIndices are not stored in view state.
                    Page.RegisterEnabledControl(this);
                }
            }
        }
 
 
        /// <devdoc>
        ///    <para> A protected method. Raises the
        ///    <see langword='SelectedIndexChanged'/> event.</para>
        /// </devdoc>
        protected virtual void OnSelectedIndexChanged(EventArgs e) {
            EventHandler onChangeHandler = (EventHandler)Events[EventSelectedIndexChanged];
            if (onChangeHandler != null) onChangeHandler(this, e);
 
            OnTextChanged(e);
        }
 
        protected virtual void OnTextChanged(EventArgs e) {
            EventHandler onChangeHandler = (EventHandler)Events[EventTextChanged];
            if (onChangeHandler != null) onChangeHandler(this,e);
        }
 
 
        /// <internalonly/>
        /// <devdoc>
        /// </devdoc>
        protected internal override void PerformDataBinding(IEnumerable dataSource) {
            base.PerformDataBinding(dataSource);
 
            if (dataSource != null) {
                bool fieldsSpecified = false;
                bool formatSpecified = false;
 
                string textField = DataTextField;
                string valueField = DataValueField;
                string textFormat = DataTextFormatString;
 
                if (!AppendDataBoundItems) {
                    Items.Clear();
                }
 
                ICollection collection = dataSource as ICollection;
                if (collection != null) {
                    Items.Capacity = collection.Count + Items.Count;
                }
 
                if ((textField.Length != 0) || (valueField.Length != 0)) {
                    fieldsSpecified = true;
                }
                if (textFormat.Length != 0) {
                    formatSpecified = true;
                }
 
                foreach (object dataItem in dataSource) {
                    ListItem item = new ListItem();
 
                    if (fieldsSpecified) {
                        if (textField.Length > 0) {
                            item.Text = DataBinder.GetPropertyValue(dataItem, textField, textFormat);
                        }
                        if (valueField.Length > 0) {
                            item.Value = DataBinder.GetPropertyValue(dataItem, valueField, null);
                        }
                    }
                    else {
                        if (formatSpecified) {
                            item.Text = String.Format(CultureInfo.CurrentCulture, textFormat, dataItem);
                        }
                        else {
                            item.Text = dataItem.ToString();
                        }
                        item.Value = dataItem.ToString();
                    }
 
                    Items.Add(item);
                }
            }
 
            // try to apply the cached SelectedIndex and SelectedValue now
            if (cachedSelectedValue != null) {
                int cachedSelectedValueIndex = -1;
 
                cachedSelectedValueIndex = Items.FindByValueInternal(cachedSelectedValue, true);
                if (-1 == cachedSelectedValueIndex) {
                    throw new ArgumentOutOfRangeException("value", SR.GetString(SR.ListControl_SelectionOutOfRange, ID, "SelectedValue"));
                }
 
                if ((cachedSelectedIndex != -1) && (cachedSelectedIndex != cachedSelectedValueIndex)) {
                    throw new ArgumentException(SR.GetString(SR.Attributes_mutually_exclusive, "SelectedIndex", "SelectedValue"));
                }
 
                SelectedIndex = cachedSelectedValueIndex;
                cachedSelectedValue = null;
                cachedSelectedIndex = -1;
            }
            else {
                if (cachedSelectedIndex != -1) {
                    SelectedIndex = cachedSelectedIndex;
                    cachedSelectedIndex = -1;
                }
            }
        }
 
        protected override void PerformSelect() {
            // Override PerformSelect and call OnDataBinding because in V1 OnDataBinding was the function that
            // performed the databind, and we need to maintain backward compat.  OnDataBinding will retrieve the
            // data from the view synchronously and call PerformDataBinding with the data, preserving the OM.
            OnDataBinding(EventArgs.Empty);
            PostPerformDataBindingAction();
        }
 
        private void PostPerformDataBindingAction() {
            if (_asyncSelectPending)
                return;
 
            RequiresDataBinding = false;
            MarkAsDataBound();
            OnDataBound(EventArgs.Empty);
        }
 
        /// <devdoc>
        /// <para>This method is used by controls and adapters
        /// to render the options inside a select statement.</para>
        /// </devdoc>
        protected internal override void RenderContents(HtmlTextWriter writer) {
            ListItemCollection liCollection = Items;
            int n = liCollection.Count;
 
            if (n > 0) {
                bool selected = false;
                for (int i=0; i < n; i++) {
                    ListItem li = liCollection[i];
 
                    if (li.Enabled == false) {
                        // the only way to disable an item in a select
                        // is to hide it
                        continue;
                    }
 
                    writer.WriteBeginTag("option");
                    if (li.Selected) {
                        if (selected) {
                            VerifyMultiSelect();
                        }
                        selected = true;
                        writer.WriteAttribute("selected", "selected");
                    }
 
                    writer.WriteAttribute("value", li.Value, true /*fEncode*/);
 
                    // VSWhidbey 163920 Render expando attributes.
                    if (li.HasAttributes) {
                        li.Attributes.Render(writer);
                    }
 
                    if (Page != null) {
                        Page.ClientScript.RegisterForEventValidation(UniqueID, li.Value);
                    }
 
                    writer.Write(HtmlTextWriter.TagRightChar);
                    HttpUtility.HtmlEncode(li.Text, writer);
                    writer.WriteEndTag("option");
                    writer.WriteLine();
                }
            }
        }
 
 
        /// <internalonly/>
        /// <devdoc>
        /// </devdoc>
        protected override object SaveViewState() {
            object baseState = base.SaveViewState();
            object items = Items.SaveViewState();
            object selectedIndicesState = null;
 
            if (SaveSelectedIndicesViewState) {
                selectedIndicesState = SelectedIndicesInternal;
            }
 
            if (selectedIndicesState != null || items != null || baseState != null) {
                return new Triplet(baseState, items, selectedIndicesState);
            }
            return null;
        }
 
 
        /// <devdoc>
        ///    Sets items within the
        ///    list to be selected according to the specified array of indexes.
        /// </devdoc>
        internal void SelectInternal(ArrayList selectedIndices) {
            ClearSelection();
            for (int i=0; i < selectedIndices.Count; i++) {
                int n = (int) selectedIndices[i];
                if (n >= 0 && n < Items.Count)
                    Items[n].Selected = true;
            }
            cachedSelectedIndices = selectedIndices;
        }
 
        internal static void SetControlToRepeatID(Control owner, Control controlToRepeat, int index) {
            string idSuffix = index.ToString(NumberFormatInfo.InvariantInfo);
            if (owner.EffectiveClientIDMode == ClientIDMode.Static) {
                if (String.IsNullOrEmpty(owner.ID)) {
                    // When IDMode=Static but has no ID, what should the item IDs be? Reverting to AutoID behavior.
                    controlToRepeat.ID = idSuffix;
                    controlToRepeat.ClientIDMode = ClientIDMode.AutoID;
                }
                else {
                    controlToRepeat.ID = owner.ID + "_" + idSuffix;
                    controlToRepeat.ClientIDMode = ClientIDMode.Inherit;
                }
            }
            else {
                controlToRepeat.ID = idSuffix;
                controlToRepeat.ClientIDMode = ClientIDMode.Inherit;
            }
        }
 
        /// <devdoc>
        ///    Sets items within the list to be selected from post data.
        ///    The difference is that these items won't be cached and reset after a databind.
        /// </devdoc>
        protected void SetPostDataSelection(int selectedIndex) {
            if (Items.Count != 0) {
                if (selectedIndex < Items.Count) {
                    ClearSelection();
                    if (selectedIndex >= 0) {
                        Items[selectedIndex].Selected = true;
                    }
                }
            }
        }
 
 
        /// <internalonly/>
        /// <devdoc>
        /// </devdoc>
        protected override void TrackViewState() {
            base.TrackViewState();
            Items.TrackViewState();
        }
 
 
        protected internal virtual void VerifyMultiSelect() {
            if (!IsMultiSelectInternal) {
                throw new HttpException(SR.GetString(SR.Cant_Multiselect_In_Single_Mode));
            }
        }
    }
}