e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
1007 lines
34 KiB
C#
1007 lines
34 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="HtmlSelect.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace System.Web.UI.HtmlControls {
|
|
using System.Runtime.Serialization.Formatters;
|
|
using System.Text;
|
|
using System.ComponentModel;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Specialized;
|
|
using System.Data;
|
|
using System.Web;
|
|
using System.Web.Util;
|
|
using System.Web.UI;
|
|
using System.Web.UI.WebControls;
|
|
using System.Globalization;
|
|
using Debug=System.Web.Util.Debug;
|
|
using System.Security.Permissions;
|
|
|
|
public class HtmlSelectBuilder : ControlBuilder {
|
|
|
|
|
|
public override Type GetChildControlType(string tagName, IDictionary attribs) {
|
|
if (StringUtil.EqualsIgnoreCase(tagName, "option"))
|
|
return typeof(ListItem);
|
|
|
|
return null;
|
|
}
|
|
|
|
|
|
public override bool AllowWhitespaceLiterals() {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// The <see langword='HtmlSelect'/>
|
|
/// class defines the methods, properties, and events for the
|
|
/// HtmlSelect control. This class allows programmatic access to the HTML
|
|
/// <select> element on the server.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[
|
|
DefaultEvent("ServerChange"),
|
|
ValidationProperty("Value"),
|
|
ControlBuilderAttribute(typeof(HtmlSelectBuilder)),
|
|
SupportsEventValidation,
|
|
]
|
|
public class HtmlSelect : HtmlContainerControl, IPostBackDataHandler, IParserAccessor {
|
|
|
|
private static readonly object EventServerChange = new object();
|
|
|
|
internal const string DataBoundViewStateKey = "_!DataBound";
|
|
|
|
private object dataSource;
|
|
private ListItemCollection items;
|
|
private int cachedSelectedIndex;
|
|
|
|
private bool _requiresDataBinding;
|
|
private bool _inited;
|
|
private bool _throwOnDataPropertyChange;
|
|
|
|
private DataSourceView _currentView;
|
|
private bool _currentViewIsFromDataSourceID;
|
|
private bool _currentViewValid;
|
|
private bool _pagePreLoadFired;
|
|
|
|
/*
|
|
* Creates an intrinsic Html SELECT control.
|
|
*/
|
|
|
|
public HtmlSelect() : base("select") {
|
|
cachedSelectedIndex = -1;
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
[
|
|
DefaultValue(""),
|
|
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
|
|
WebCategory("Data"),
|
|
WebSysDescription(SR.HtmlSelect_DataMember)
|
|
]
|
|
public virtual string DataMember {
|
|
get {
|
|
object o = ViewState["DataMember"];
|
|
if (o != null)
|
|
return (string)o;
|
|
return String.Empty;
|
|
}
|
|
set {
|
|
Attributes["DataMember"] = MapStringAttributeToString(value);
|
|
OnDataPropertyChanged();
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// Gets or sets the data source to databind the list values
|
|
/// in the <see langword='HtmlSelect'/> control against. This provides data to
|
|
/// populate the select list with items.
|
|
/// </devdoc>
|
|
[
|
|
WebCategory("Data"),
|
|
DefaultValue(null),
|
|
WebSysDescription(SR.BaseDataBoundControl_DataSource),
|
|
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
|
|
]
|
|
public virtual object DataSource {
|
|
get {
|
|
return dataSource;
|
|
}
|
|
set {
|
|
if ((value == null) || (value is IListSource) || (value is IEnumerable)) {
|
|
dataSource = value;
|
|
OnDataPropertyChanged();
|
|
}
|
|
else {
|
|
throw new ArgumentException(SR.GetString(SR.Invalid_DataSource_Type, ID));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// The ID of the DataControl that this control should use to retrieve
|
|
/// its data source. When the control is bound to a DataControl, it
|
|
/// can retrieve a data source instance on-demand, and thereby attempt
|
|
/// to work in auto-DataBind mode.
|
|
/// </summary>
|
|
[
|
|
DefaultValue(""),
|
|
WebCategory("Data"),
|
|
WebSysDescription(SR.BaseDataBoundControl_DataSourceID),
|
|
]
|
|
public virtual string DataSourceID {
|
|
get {
|
|
object o = ViewState["DataSourceID"];
|
|
if (o != null) {
|
|
return (string)o;
|
|
}
|
|
return String.Empty;
|
|
}
|
|
set {
|
|
ViewState["DataSourceID"] = value;
|
|
OnDataPropertyChanged();
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets or sets the field in the data source that provides
|
|
/// the text for an option entry in the HtmlSelect control.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[
|
|
WebCategory("Data"),
|
|
DefaultValue(""),
|
|
WebSysDescription(SR.HtmlSelect_DataTextField)
|
|
]
|
|
public virtual string DataTextField {
|
|
get {
|
|
string s = Attributes["DataTextField"];
|
|
return((s == null) ? String.Empty : s);
|
|
}
|
|
set {
|
|
Attributes["DataTextField"] = MapStringAttributeToString(value);
|
|
if (_inited) {
|
|
RequiresDataBinding = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets or sets the field in the data source that provides
|
|
/// the option item value for the <see langword='HtmlSelect'/>
|
|
/// control.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[
|
|
WebCategory("Data"),
|
|
DefaultValue(""),
|
|
WebSysDescription(SR.HtmlSelect_DataValueField)
|
|
]
|
|
public virtual string DataValueField {
|
|
get {
|
|
string s = Attributes["DataValueField"];
|
|
return((s == null) ? String.Empty : s);
|
|
}
|
|
set {
|
|
Attributes["DataValueField"] = MapStringAttributeToString(value);
|
|
if (_inited) {
|
|
RequiresDataBinding = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
public override string InnerHtml {
|
|
get {
|
|
throw new NotSupportedException(SR.GetString(SR.InnerHtml_not_supported, this.GetType().Name));
|
|
}
|
|
set {
|
|
throw new NotSupportedException(SR.GetString(SR.InnerHtml_not_supported, this.GetType().Name));
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
public override string InnerText {
|
|
get {
|
|
throw new NotSupportedException(SR.GetString(SR.InnerText_not_supported, this.GetType().Name));
|
|
}
|
|
set {
|
|
throw new NotSupportedException(SR.GetString(SR.InnerText_not_supported, this.GetType().Name));
|
|
}
|
|
}
|
|
|
|
|
|
protected bool IsBoundUsingDataSourceID {
|
|
get {
|
|
return (DataSourceID.Length > 0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* A collection containing the list of items.
|
|
*/
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets the list of option items in an <see langword='HtmlSelect'/> control.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[
|
|
Browsable(false),
|
|
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
|
|
]
|
|
public ListItemCollection Items {
|
|
get {
|
|
if (items == null) {
|
|
items = new ListItemCollection();
|
|
if (IsTrackingViewState)
|
|
((IStateManager)items).TrackViewState();
|
|
}
|
|
return items;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Multi-select property.
|
|
*/
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets or sets a value indicating whether multiple option items can be selected
|
|
/// from the list.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[
|
|
WebCategory("Behavior"),
|
|
DefaultValue(""),
|
|
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
|
|
]
|
|
public bool Multiple {
|
|
get {
|
|
string s = Attributes["multiple"];
|
|
return((s != null) ? (s.Equals("multiple")) : false);
|
|
}
|
|
|
|
set {
|
|
if (value)
|
|
Attributes["multiple"] = "multiple";
|
|
else
|
|
Attributes["multiple"] = null;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Name property.
|
|
*/
|
|
|
|
[
|
|
WebCategory("Behavior"),
|
|
DefaultValue(""),
|
|
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
|
|
]
|
|
public string Name {
|
|
get {
|
|
return UniqueID;
|
|
//string s = Attributes["name"];
|
|
//return ((s != null) ? s : "");
|
|
}
|
|
set {
|
|
//Attributes["name"] = MapStringAttributeToString(value);
|
|
}
|
|
}
|
|
|
|
// Value that gets rendered for the Name attribute
|
|
internal string RenderedNameAttribute {
|
|
get {
|
|
return Name;
|
|
//string name = Name;
|
|
//if (name.Length == 0)
|
|
// return UniqueID;
|
|
|
|
//return name;
|
|
}
|
|
}
|
|
|
|
|
|
protected bool RequiresDataBinding {
|
|
get {
|
|
return _requiresDataBinding;
|
|
}
|
|
set {
|
|
_requiresDataBinding = value;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The index of the selected item.
|
|
* Returns the first selected item if list is multi-select.
|
|
* Returns -1 if there is no selected item.
|
|
*/
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets or sets the ordinal index of the selected option item in an
|
|
/// <see langword='HtmlSelect'/> control. If multiple items are selected, this
|
|
/// property holds the index of the first item selected in the list.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[
|
|
Browsable(false),
|
|
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
|
|
HtmlControlPersistable(false),
|
|
]
|
|
public virtual int SelectedIndex {
|
|
get {
|
|
for (int i=0; i < Items.Count; i++) {
|
|
if (Items[i].Selected)
|
|
return i;
|
|
}
|
|
if (Size <= 1 && !Multiple) {
|
|
// SELECT as a dropdown must have a selection
|
|
if (Items.Count > 0)
|
|
Items[0].Selected = true;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
set {
|
|
// if we have no items, save the selectedindex
|
|
// for later databinding
|
|
if (Items.Count == 0) {
|
|
cachedSelectedIndex = value;
|
|
}
|
|
else {
|
|
if (value < -1 || value >= Items.Count) {
|
|
throw new ArgumentOutOfRangeException("value");
|
|
}
|
|
ClearSelection();
|
|
if (value >= 0)
|
|
Items[value].Selected = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* SelectedIndices property.
|
|
* Protected property for getting array of selected indices.
|
|
*/
|
|
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
protected virtual int[] SelectedIndices {
|
|
get {
|
|
int n = 0;
|
|
int[] temp = new int[3];
|
|
for (int i=0; i < Items.Count; i++) {
|
|
if (Items[i].Selected == true) {
|
|
if (n == temp.Length) {
|
|
int[] t = new int[n+n];
|
|
temp.CopyTo(t,0);
|
|
temp = t;
|
|
}
|
|
temp[n++] = i;
|
|
}
|
|
}
|
|
int[] selectedIndices = new int[n];
|
|
Array.Copy(temp,0,selectedIndices,0,n);
|
|
return selectedIndices;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The size of the list.
|
|
* A size of 1 displays a dropdown list.
|
|
*/
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets or sets the number of option items visible in the browser at a time. A
|
|
/// value greater that one will typically cause browsers to display a scrolling
|
|
/// list.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[
|
|
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
|
|
]
|
|
public int Size {
|
|
get {
|
|
string s = Attributes["size"];
|
|
return((s != null) ? Int32.Parse(s, CultureInfo.InvariantCulture) : -1);
|
|
}
|
|
|
|
set {
|
|
Attributes["size"] = MapIntegerAttributeToString(value);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Value property.
|
|
*/
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets or sets the current item selected in the <see langword='HtmlSelect'/>
|
|
/// control.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[
|
|
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
|
|
]
|
|
public string Value {
|
|
get {
|
|
int i = SelectedIndex;
|
|
return(i < 0 || i >= Items.Count) ? String.Empty : Items[i].Value;
|
|
}
|
|
|
|
set {
|
|
int i = Items.FindByValueInternal(value, true);
|
|
if (i >= 0)
|
|
SelectedIndex = i;
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Occurs when an <see langword='HtmlSelect'/> control is changed on the
|
|
/// server.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[
|
|
WebCategory("Action"),
|
|
WebSysDescription(SR.HtmlSelect_OnServerChange)
|
|
]
|
|
public event EventHandler ServerChange {
|
|
add {
|
|
Events.AddHandler(EventServerChange, value);
|
|
}
|
|
remove {
|
|
Events.RemoveHandler(EventServerChange, value);
|
|
}
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
protected override void AddParsedSubObject(object obj) {
|
|
if (obj is ListItem)
|
|
Items.Add((ListItem)obj);
|
|
else
|
|
throw new HttpException(SR.GetString(SR.Cannot_Have_Children_Of_Type, "HtmlSelect", obj.GetType().Name));
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
protected virtual void ClearSelection() {
|
|
for (int i=0; i < Items.Count; i++)
|
|
Items[i].Selected = false;
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Connects this data bound control to the appropriate DataSourceView
|
|
/// and hooks up the appropriate event listener for the
|
|
/// DataSourceViewChanged event. The return value is the new view (if
|
|
/// any) that was connected to. An exception is thrown if there is
|
|
/// a problem finding the requested view or data source.
|
|
/// </devdoc>
|
|
private DataSourceView ConnectToDataSourceView() {
|
|
if (_currentViewValid && !DesignMode) {
|
|
// If the current view is correct, there is no need to reconnect
|
|
return _currentView;
|
|
}
|
|
|
|
// Disconnect from old view, if necessary
|
|
if ((_currentView != null) && (_currentViewIsFromDataSourceID)) {
|
|
// We only care about this event if we are bound through the DataSourceID property
|
|
_currentView.DataSourceViewChanged -= new EventHandler(OnDataSourceViewChanged);
|
|
}
|
|
|
|
// Connect to new view
|
|
IDataSource ds = null;
|
|
string dataSourceID = DataSourceID;
|
|
|
|
if (dataSourceID.Length != 0) {
|
|
// Try to find a DataSource control with the ID specified in DataSourceID
|
|
Control control = DataBoundControlHelper.FindControl(this, dataSourceID);
|
|
if (control == null) {
|
|
throw new HttpException(SR.GetString(SR.DataControl_DataSourceDoesntExist, ID, dataSourceID));
|
|
}
|
|
ds = control as IDataSource;
|
|
if (ds == null) {
|
|
throw new HttpException(SR.GetString(SR.DataControl_DataSourceIDMustBeDataControl, ID, dataSourceID));
|
|
}
|
|
}
|
|
|
|
if (ds == null) {
|
|
// DataSource control was not found, construct a temporary data source to wrap the data
|
|
ds = new ReadOnlyDataSource(DataSource, DataMember);
|
|
}
|
|
else {
|
|
// Ensure that both DataSourceID as well as DataSource are not set at the same time
|
|
if (DataSource != null) {
|
|
throw new InvalidOperationException(SR.GetString(SR.DataControl_MultipleDataSources, ID));
|
|
}
|
|
}
|
|
|
|
// IDataSource was found, extract the appropriate view and return it
|
|
DataSourceView newView = ds.GetView(DataMember);
|
|
if (newView == null) {
|
|
throw new InvalidOperationException(SR.GetString(SR.DataControl_ViewNotFound, ID));
|
|
}
|
|
|
|
_currentViewIsFromDataSourceID = IsBoundUsingDataSourceID;
|
|
_currentView = newView;
|
|
if ((_currentView != null) && (_currentViewIsFromDataSourceID)) {
|
|
// We only care about this event if we are bound through the DataSourceID property
|
|
_currentView.DataSourceViewChanged += new EventHandler(OnDataSourceViewChanged);
|
|
}
|
|
_currentViewValid = true;
|
|
|
|
return _currentView;
|
|
}
|
|
|
|
|
|
protected override ControlCollection CreateControlCollection() {
|
|
return new EmptyControlCollection(this);
|
|
}
|
|
|
|
|
|
protected void EnsureDataBound() {
|
|
try {
|
|
_throwOnDataPropertyChange = true;
|
|
if (RequiresDataBinding && DataSourceID.Length > 0) {
|
|
DataBind();
|
|
}
|
|
}
|
|
finally{
|
|
_throwOnDataPropertyChange = false;
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// Returns an IEnumerable that is the DataSource, which either came
|
|
/// from the DataSource property or from the control bound via the
|
|
/// DataSourceID property.
|
|
/// </devdoc>
|
|
protected virtual IEnumerable GetData() {
|
|
DataSourceView view = ConnectToDataSourceView();
|
|
|
|
Debug.Assert(_currentViewValid);
|
|
|
|
if (view != null) {
|
|
return view.ExecuteSelect(DataSourceSelectArguments.Empty);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/*
|
|
* Override to load items and selected indices.
|
|
*/
|
|
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
protected override void LoadViewState(object savedState) {
|
|
if (savedState != null) {
|
|
Triplet statetriplet = (Triplet)savedState;
|
|
base.LoadViewState(statetriplet.First);
|
|
|
|
// restore state of items
|
|
((IStateManager)Items).LoadViewState(statetriplet.Second);
|
|
|
|
// restore selected indices
|
|
object selectedIndices = statetriplet.Third;
|
|
if (selectedIndices != null)
|
|
Select((int[])selectedIndices);
|
|
}
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
protected override void OnDataBinding(EventArgs e) {
|
|
base.OnDataBinding(e);
|
|
|
|
// create items using the datasource
|
|
IEnumerable dataSource = GetData();
|
|
|
|
// create items using the datasource
|
|
if (dataSource != null) {
|
|
bool fieldsSpecified = false;
|
|
string textField = DataTextField;
|
|
string valueField = DataValueField;
|
|
|
|
Items.Clear();
|
|
ICollection collection = dataSource as ICollection;
|
|
if (collection != null) {
|
|
Items.Capacity = collection.Count;
|
|
}
|
|
|
|
if ((textField.Length != 0) || (valueField.Length != 0))
|
|
fieldsSpecified = true;
|
|
|
|
foreach (object dataItem in dataSource) {
|
|
ListItem item = new ListItem();
|
|
|
|
if (fieldsSpecified) {
|
|
if (textField.Length > 0) {
|
|
item.Text = DataBinder.GetPropertyValue(dataItem,textField,null);
|
|
}
|
|
if (valueField.Length > 0) {
|
|
item.Value = DataBinder.GetPropertyValue(dataItem,valueField,null);
|
|
}
|
|
}
|
|
else {
|
|
item.Text = item.Value = dataItem.ToString();
|
|
}
|
|
|
|
Items.Add(item);
|
|
}
|
|
}
|
|
// try to apply the cached SelectedIndex now
|
|
if (cachedSelectedIndex != -1) {
|
|
SelectedIndex = cachedSelectedIndex;
|
|
cachedSelectedIndex = -1;
|
|
}
|
|
ViewState[DataBoundViewStateKey] = true;
|
|
RequiresDataBinding = false;
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// This method is called when DataMember, DataSource, or DataSourceID is changed.
|
|
/// </devdoc>
|
|
protected virtual void OnDataPropertyChanged() {
|
|
if (_throwOnDataPropertyChange) {
|
|
throw new HttpException(SR.GetString(SR.DataBoundControl_InvalidDataPropertyChange, ID));
|
|
}
|
|
|
|
if (_inited) {
|
|
RequiresDataBinding = true;
|
|
}
|
|
_currentViewValid = false;
|
|
}
|
|
|
|
|
|
protected virtual void OnDataSourceViewChanged(object sender, EventArgs e) {
|
|
RequiresDataBinding = true;
|
|
}
|
|
|
|
|
|
protected internal override void OnInit(EventArgs e) {
|
|
base.OnInit(e);
|
|
|
|
if (Page != null) {
|
|
Page.PreLoad += new EventHandler(this.OnPagePreLoad);
|
|
if (!IsViewStateEnabled && Page.IsPostBack) {
|
|
RequiresDataBinding = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
protected internal override void OnLoad(EventArgs e) {
|
|
_inited = true; // just in case we were added to the page after PreLoad
|
|
ConnectToDataSourceView();
|
|
if (Page != null && !_pagePreLoadFired && ViewState[DataBoundViewStateKey] == null) {
|
|
// If the control was added after PagePreLoad, we still need to databind it because it missed its
|
|
// first change in PagePreLoad. If this control was created by a call to a parent control's DataBind
|
|
// in Page_Load (with is relatively common), this control will already have been databound even
|
|
// though pagePreLoad never fired and the page isn't a postback.
|
|
if (!Page.IsPostBack) {
|
|
RequiresDataBinding = true;
|
|
}
|
|
|
|
// If the control was added to the page after page.PreLoad, we'll never get the event and we'll
|
|
// never databind the control. So if we're catching up and Load happens but PreLoad never happened,
|
|
// call DataBind. This may make the control get databound twice if the user called DataBind on the control
|
|
// directly in Page.OnLoad, but better to bind twice than never to bind at all.
|
|
else if (IsViewStateEnabled) {
|
|
RequiresDataBinding = true;
|
|
}
|
|
}
|
|
|
|
base.OnLoad(e);
|
|
}
|
|
|
|
private void OnPagePreLoad(object sender, EventArgs e) {
|
|
_inited = true;
|
|
|
|
if (Page != null) {
|
|
Page.PreLoad -= new EventHandler(this.OnPagePreLoad);
|
|
|
|
// Setting RequiresDataBinding to true in OnLoad is too late because the OnLoad page event
|
|
// happens before the control.OnLoad method gets called. So a page_load handler on the page
|
|
// that calls DataBind won't prevent DataBind from getting called again in PreRender.
|
|
if (!Page.IsPostBack) {
|
|
RequiresDataBinding = true;
|
|
}
|
|
// If this is a postback and viewstate is enabled, but we have never bound the control
|
|
// before, it is probably because its visibility was changed in the postback. In this
|
|
// case, we need to bind the control or it will never appear. This is a common scenario
|
|
// for Wizard and MultiView.
|
|
if (Page.IsPostBack && IsViewStateEnabled && ViewState[DataBoundViewStateKey] == null) {
|
|
RequiresDataBinding = true;
|
|
}
|
|
}
|
|
_pagePreLoadFired = true;
|
|
}
|
|
|
|
/*
|
|
* This method is invoked just prior to rendering.
|
|
*/
|
|
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
protected internal override void OnPreRender(EventArgs e) {
|
|
base.OnPreRender(e);
|
|
|
|
// An Html SELECT does not post when nothing is selected.
|
|
if (Page != null && !Disabled) {
|
|
if (Size > 1) {
|
|
Page.RegisterRequiresPostBack(this);
|
|
}
|
|
|
|
Page.RegisterEnabledControl(this);
|
|
}
|
|
|
|
EnsureDataBound();
|
|
}
|
|
|
|
/*
|
|
* Method used to raise the OnServerChange event.
|
|
*/
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Raised
|
|
/// on the server when the <see langword='HtmlSelect'/> control list values
|
|
/// change between postback requests.
|
|
/// </para>
|
|
/// </devdoc>
|
|
protected virtual void OnServerChange(EventArgs e) {
|
|
EventHandler handler = (EventHandler)Events[EventServerChange];
|
|
if (handler != null) handler(this, e);
|
|
}
|
|
|
|
/*
|
|
* Override to prevent SelectedIndex from being rendered as an attribute.
|
|
*/
|
|
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
protected override void RenderAttributes(HtmlTextWriter writer) {
|
|
if (Page != null) {
|
|
Page.ClientScript.RegisterForEventValidation(RenderedNameAttribute);
|
|
}
|
|
|
|
writer.WriteAttribute("name", RenderedNameAttribute);
|
|
Attributes.Remove("name");
|
|
|
|
Attributes.Remove("DataValueField");
|
|
Attributes.Remove("DataTextField");
|
|
Attributes.Remove("DataMember");
|
|
Attributes.Remove("DataSourceID");
|
|
base.RenderAttributes(writer);
|
|
}
|
|
|
|
/*
|
|
* Render the Items in the list.
|
|
*/
|
|
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
protected internal override void RenderChildren(HtmlTextWriter writer) {
|
|
bool selected = false;
|
|
bool isSingle = !Multiple;
|
|
|
|
writer.WriteLine();
|
|
writer.Indent++;
|
|
ListItemCollection liCollection = Items;
|
|
int n = liCollection.Count;
|
|
if (n > 0) {
|
|
for (int i=0; i < n; i++) {
|
|
ListItem li = liCollection[i];
|
|
writer.WriteBeginTag("option");
|
|
if (li.Selected) {
|
|
if (isSingle)
|
|
{
|
|
if (selected)
|
|
throw new HttpException(SR.GetString(SR.HtmlSelect_Cant_Multiselect_In_Single_Mode));
|
|
selected=true;
|
|
}
|
|
writer.WriteAttribute("selected", "selected");
|
|
}
|
|
|
|
writer.WriteAttribute("value", li.Value, true /*fEncode*/);
|
|
|
|
// This is to fix the case where the user puts one of these
|
|
// three values in the AttributeCollection. Removing them
|
|
// at least is better than rendering them twice.
|
|
li.Attributes.Remove("text");
|
|
li.Attributes.Remove("value");
|
|
li.Attributes.Remove("selected");
|
|
|
|
li.Attributes.Render(writer);
|
|
writer.Write(HtmlTextWriter.TagRightChar);
|
|
HttpUtility.HtmlEncode(li.Text, writer);
|
|
writer.WriteEndTag("option");
|
|
writer.WriteLine();
|
|
}
|
|
}
|
|
writer.Indent--;
|
|
}
|
|
|
|
/*
|
|
* Save selected indices and modified Items.
|
|
*/
|
|
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
protected override object SaveViewState() {
|
|
|
|
object baseState = base.SaveViewState();
|
|
object items = ((IStateManager)Items).SaveViewState();
|
|
object selectedindices = null;
|
|
|
|
// only save selection if handler is registered,
|
|
// we are disabled, or we are not visible
|
|
// since selection is always posted back otherwise
|
|
if (Events[EventServerChange] != null || Disabled || !Visible)
|
|
selectedindices = SelectedIndices;
|
|
|
|
if (selectedindices != null || items != null || baseState != null)
|
|
return new Triplet(baseState, items, selectedindices);
|
|
|
|
return null;
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
protected virtual void Select(int[] selectedIndices) {
|
|
ClearSelection();
|
|
for (int i=0; i < selectedIndices.Length; i++) {
|
|
int n = selectedIndices[i];
|
|
if (n >= 0 && n < Items.Count)
|
|
Items[n].Selected = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* TrackState
|
|
*/
|
|
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
protected override void TrackViewState() {
|
|
base.TrackViewState();
|
|
((IStateManager)Items).TrackViewState();
|
|
}
|
|
|
|
|
|
/*
|
|
* Method of IPostBackDataHandler interface to process posted data.
|
|
* SelectList processes a newly posted value.
|
|
*/
|
|
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection) {
|
|
return LoadPostData(postDataKey, postCollection);
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) {
|
|
string[] selectedItems = postCollection.GetValues(postDataKey);
|
|
bool selectionChanged = false;
|
|
|
|
if (selectedItems != null) {
|
|
if (!Multiple) {
|
|
int n = Items.FindByValueInternal(selectedItems[0], false);
|
|
if (SelectedIndex != n) {
|
|
SelectedIndex = n;
|
|
selectionChanged = true;
|
|
}
|
|
}
|
|
else { // multiple selection
|
|
int count = selectedItems.Length;
|
|
int[] oldSelectedIndices = SelectedIndices;
|
|
int[] newSelectedIndices = new int[count];
|
|
for (int i=0; i < count; i++) {
|
|
// create array of new indices from posted values
|
|
newSelectedIndices[i] = Items.FindByValueInternal(selectedItems[i], false);
|
|
}
|
|
|
|
if (oldSelectedIndices.Length == count) {
|
|
// check new indices against old indices
|
|
// assumes selected values are posted in order
|
|
for (int i=0; i < count; i++) {
|
|
if (newSelectedIndices[i] != oldSelectedIndices[i]) {
|
|
selectionChanged = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// indices must have changed if count is different
|
|
selectionChanged = true;
|
|
}
|
|
|
|
if (selectionChanged) {
|
|
// select new indices
|
|
Select(newSelectedIndices);
|
|
}
|
|
}
|
|
}
|
|
else { // no items selected
|
|
if (SelectedIndex != -1) {
|
|
SelectedIndex = -1;
|
|
selectionChanged = true;
|
|
}
|
|
}
|
|
|
|
if (selectionChanged) {
|
|
ValidateEvent(postDataKey);
|
|
}
|
|
|
|
return selectionChanged;
|
|
}
|
|
|
|
/*
|
|
* Method of IPostBackDataHandler interface which is invoked whenever posted data
|
|
* for a control has changed. SelectList fires an OnServerChange event.
|
|
*/
|
|
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
void IPostBackDataHandler.RaisePostDataChangedEvent() {
|
|
RaisePostDataChangedEvent();
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// </devdoc>
|
|
protected virtual void RaisePostDataChangedEvent() {
|
|
OnServerChange(EventArgs.Empty);
|
|
}
|
|
}
|
|
}
|