ListControl.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / whidbey / NetFXspW7 / ndp / fx / src / xsp / System / Web / UI / WebControls / ListControl.cs / 1 / ListControl.cs

                            //------------------------------------------------------------------------------ 
// 
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//----------------------------------------------------------------------------- 

namespace System.Web.UI.WebControls { 
 
    using System;
    using System.Collections; 
    using System.ComponentModel;
    using System.Globalization;
    using System.Security.Permissions;
    using System.Web; 
    using System.Web.UI;
    using System.Web.UI.Adapters; 
    using System.Web.Util; 
    using System.Drawing;
    using System.Drawing.Design; 


    /// 
    ///    An abstract base class. Defines the common 
    ///       properties, methods, and events for all list-type controls.
    ///  
    [ 
    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)
    ] 
    [AspNetHostingPermission(SecurityAction.LinkDemand, Level=AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level=AspNetHostingPermissionLevel.Minimal)] 
    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; 

 
        /// 
        /// Initializes a new instance of the  class.
        /// 
        public ListControl() { 
            cachedSelectedIndex = -1;
        } 
 
        /// 
        ///     Gets or sets a value 
        ///       indicating whether databound items will be added to the list of staticly-declared
        ///       items in the list.
        /// 
        [ 
        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;
                } 
            }
        } 
 

        ///  
        ///     Gets or sets a value
        ///       indicating whether an automatic postback to the server will occur whenever the
        ///       user changes the selection of the list.
        ///  
        [
        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; 
            }
        } 


        /// 
        ///     Indicates the field of the 
        ///       data source that provides the text content of the list items.
        ///  
        [ 
        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;
                } 
            }
        } 
 

        ///  
        /// 
        [
        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; 
                }
            } 
        }


        ///  
        ///    Indicates the field of the data source that provides the value content of the
        ///       list items. 
        ///  
        [
        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; 
                }
            } 
        } 

        ///  
        ///    A protected property. Indicates if the ListControl supports multiple selections
        /// 
        internal virtual bool IsMultiSelectInternal {
            get  { 
                return false;
            } 
        } 

 

        /// 
        ///    
        ///       Indicates the collection of items within the list. 
        ///       This property
        ///       is read-only. 
        ///  
        [
        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;
            } 
        } 

 
        /// 
        ///    Determines whether the SelectedIndices must be stored in view state, to
        ///    optimize the size of the saved state.
        ///  
        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;
                }

#if ORCAS 
                if (VirtualStartPage != VirtualEndPage) {
                    return true; 
                } 
#endif
 
                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; 
            }
        }

 
        /// 
        ///    Indicates the ordinal index of the first selected item within the 
        ///       list. 
        /// 
        [ 
        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;
            }
        }
 

        ///  
        ///    A protected property. Gets an array of selected 
        ///       indexes within the list. This property is read-only.
        ///  
        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; 
            }
        }

 

        ///  
        ///    Indicates the first selected item within the list. 
        ///       This property is read-only.
        ///  
        [
        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];
            }
        } 

 
 
        /// 
        ///    Indicates the value of the first selected item within the 
        ///       list.
        /// 
        [
        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;
            }
        } 

 
        ///  
        ///    Occurs when the list selection is changed upon server
        ///    postback. 
        /// 
        [
        WebCategory("Action"),
        WebSysDescription(SR.ListControl_OnSelectedIndexChanged) 
        ]
        public event EventHandler SelectedIndexChanged { 
            add { 
                Events.AddHandler(EventSelectedIndexChanged, value);
            } 
            remove {
                Events.RemoveHandler(EventSelectedIndexChanged, value);
            }
        } 

 
        ///  
        ///    Occurs when the content of the text box is
        ///       changed upon server postback. 
        /// 
        [
        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) {
                // 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); 
        }


        ///  
        ///     Clears out the list selection and sets the
        ///     property 
        ///       of all items to false. 
        /// 
        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.
        } 
 

        ///  
        /// 
        ///    Load previously saved state.
        ///    Overridden to restore selection.
        ///  
        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;
        } 
 
        protected override void OnDataBinding(EventArgs e) {
            base.OnDataBinding(e); 
            IEnumerable data = GetData().ExecuteSelect(DataSourceSelectArguments.Empty);
            PerformDataBinding(data);
        }
 

        ///  
        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); 
                } 
            }
        } 


        /// 
        ///     A protected method. Raises the 
        ///     event.
        ///  
        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); 
        }
 

        /// 
        /// 
        ///  
        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);
            RequiresDataBinding = false; 
            MarkAsDataBound(); 
            OnDataBound(EventArgs.Empty);
        } 

        /// 
        /// This method is used by controls and adapters
        /// to render the options inside a select statement. 
        /// 
        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();
                } 
            }
        }

 
        /// 
        ///  
        ///  
        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;
        }
 

        ///  
        ///    Sets items within the 
        ///    list to be selected according to the specified array of indexes.
        ///  
        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;
        } 

        /// 
        ///    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. 
        /// 
        protected void SetPostDataSelection(int selectedIndex) { 
            if (Items.Count != 0) { 
                if (selectedIndex < Items.Count) {
                    ClearSelection(); 
                    if (selectedIndex >= 0) {
                        Items[selectedIndex].Selected = true;
                    }
                } 
            }
        } 
 

        ///  
        /// 
        /// 
        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));
            }
        } 
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//------------------------------------------------------------------------------ 
// 
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//----------------------------------------------------------------------------- 

namespace System.Web.UI.WebControls { 
 
    using System;
    using System.Collections; 
    using System.ComponentModel;
    using System.Globalization;
    using System.Security.Permissions;
    using System.Web; 
    using System.Web.UI;
    using System.Web.UI.Adapters; 
    using System.Web.Util; 
    using System.Drawing;
    using System.Drawing.Design; 


    /// 
    ///    An abstract base class. Defines the common 
    ///       properties, methods, and events for all list-type controls.
    ///  
    [ 
    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)
    ] 
    [AspNetHostingPermission(SecurityAction.LinkDemand, Level=AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level=AspNetHostingPermissionLevel.Minimal)] 
    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; 

 
        /// 
        /// Initializes a new instance of the  class.
        /// 
        public ListControl() { 
            cachedSelectedIndex = -1;
        } 
 
        /// 
        ///     Gets or sets a value 
        ///       indicating whether databound items will be added to the list of staticly-declared
        ///       items in the list.
        /// 
        [ 
        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;
                } 
            }
        } 
 

        ///  
        ///     Gets or sets a value
        ///       indicating whether an automatic postback to the server will occur whenever the
        ///       user changes the selection of the list.
        ///  
        [
        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; 
            }
        } 


        /// 
        ///     Indicates the field of the 
        ///       data source that provides the text content of the list items.
        ///  
        [ 
        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;
                } 
            }
        } 
 

        ///  
        /// 
        [
        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; 
                }
            } 
        }


        ///  
        ///    Indicates the field of the data source that provides the value content of the
        ///       list items. 
        ///  
        [
        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; 
                }
            } 
        } 

        ///  
        ///    A protected property. Indicates if the ListControl supports multiple selections
        /// 
        internal virtual bool IsMultiSelectInternal {
            get  { 
                return false;
            } 
        } 

 

        /// 
        ///    
        ///       Indicates the collection of items within the list. 
        ///       This property
        ///       is read-only. 
        ///  
        [
        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;
            } 
        } 

 
        /// 
        ///    Determines whether the SelectedIndices must be stored in view state, to
        ///    optimize the size of the saved state.
        ///  
        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;
                }

#if ORCAS 
                if (VirtualStartPage != VirtualEndPage) {
                    return true; 
                } 
#endif
 
                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; 
            }
        }

 
        /// 
        ///    Indicates the ordinal index of the first selected item within the 
        ///       list. 
        /// 
        [ 
        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;
            }
        }
 

        ///  
        ///    A protected property. Gets an array of selected 
        ///       indexes within the list. This property is read-only.
        ///  
        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; 
            }
        }

 

        ///  
        ///    Indicates the first selected item within the list. 
        ///       This property is read-only.
        ///  
        [
        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];
            }
        } 

 
 
        /// 
        ///    Indicates the value of the first selected item within the 
        ///       list.
        /// 
        [
        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;
            }
        } 

 
        ///  
        ///    Occurs when the list selection is changed upon server
        ///    postback. 
        /// 
        [
        WebCategory("Action"),
        WebSysDescription(SR.ListControl_OnSelectedIndexChanged) 
        ]
        public event EventHandler SelectedIndexChanged { 
            add { 
                Events.AddHandler(EventSelectedIndexChanged, value);
            } 
            remove {
                Events.RemoveHandler(EventSelectedIndexChanged, value);
            }
        } 

 
        ///  
        ///    Occurs when the content of the text box is
        ///       changed upon server postback. 
        /// 
        [
        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) {
                // 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); 
        }


        ///  
        ///     Clears out the list selection and sets the
        ///     property 
        ///       of all items to false. 
        /// 
        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.
        } 
 

        ///  
        /// 
        ///    Load previously saved state.
        ///    Overridden to restore selection.
        ///  
        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;
        } 
 
        protected override void OnDataBinding(EventArgs e) {
            base.OnDataBinding(e); 
            IEnumerable data = GetData().ExecuteSelect(DataSourceSelectArguments.Empty);
            PerformDataBinding(data);
        }
 

        ///  
        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); 
                } 
            }
        } 


        /// 
        ///     A protected method. Raises the 
        ///     event.
        ///  
        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); 
        }
 

        /// 
        /// 
        ///  
        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);
            RequiresDataBinding = false; 
            MarkAsDataBound(); 
            OnDataBound(EventArgs.Empty);
        } 

        /// 
        /// This method is used by controls and adapters
        /// to render the options inside a select statement. 
        /// 
        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();
                } 
            }
        }

 
        /// 
        ///  
        ///  
        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;
        }
 

        ///  
        ///    Sets items within the 
        ///    list to be selected according to the specified array of indexes.
        ///  
        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;
        } 

        /// 
        ///    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. 
        /// 
        protected void SetPostDataSelection(int selectedIndex) { 
            if (Items.Count != 0) { 
                if (selectedIndex < Items.Count) {
                    ClearSelection(); 
                    if (selectedIndex >= 0) {
                        Items[selectedIndex].Selected = true;
                    }
                } 
            }
        } 
 

        ///  
        /// 
        /// 
        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));
            }
        } 
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK