565 lines
19 KiB
C#
565 lines
19 KiB
C#
|
//------------------------------------------------------------------------------
|
||
|
// <copyright file="CheckBoxList.cs" company="Microsoft">
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
// </copyright>
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
namespace System.Web.UI.WebControls {
|
||
|
|
||
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Specialized;
|
||
|
using System.ComponentModel;
|
||
|
using System.Globalization;
|
||
|
using System.Web;
|
||
|
using System.Web.UI;
|
||
|
using System.Web.Util;
|
||
|
|
||
|
/// <devdoc>
|
||
|
/// <para>Creates a group of <see cref='System.Web.UI.WebControls.CheckBox'/> controls.</para>
|
||
|
/// </devdoc>
|
||
|
public class CheckBoxList : ListControl, IRepeatInfoUser, INamingContainer, IPostBackDataHandler {
|
||
|
private CheckBox _controlToRepeat;
|
||
|
private string _oldAccessKey;
|
||
|
private bool _hasNotifiedOfChange;
|
||
|
private bool _cachedRegisterEnabled;
|
||
|
private bool _cachedIsEnabled;
|
||
|
|
||
|
/// <devdoc>
|
||
|
/// <para>
|
||
|
/// Initializes a new instance of the <see cref='System.Web.UI.WebControls.CheckBoxList'/> class.
|
||
|
/// </para>
|
||
|
/// </devdoc>
|
||
|
public CheckBoxList() {
|
||
|
_controlToRepeat = new CheckBox();
|
||
|
_controlToRepeat.EnableViewState = false;
|
||
|
|
||
|
// Whidbey 28457: We need to set a default numeric ID for the case
|
||
|
// of an empty checkbox list. It is because the child CheckBox
|
||
|
// always registers itself to Page as a PostBackData control and
|
||
|
// during postback it will invoke LoadPostData in this class and the
|
||
|
// method always assumes the ID is numeric. This default ID setting
|
||
|
// has been done in this way since V1.
|
||
|
_controlToRepeat.ID = "0";
|
||
|
|
||
|
Controls.Add(_controlToRepeat);
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <devdoc>
|
||
|
/// <para>
|
||
|
/// Gets or sets
|
||
|
/// the padding between each item.
|
||
|
/// </para>
|
||
|
/// </devdoc>
|
||
|
[
|
||
|
WebCategory("Layout"),
|
||
|
DefaultValue(-1),
|
||
|
WebSysDescription(SR.CheckBoxList_CellPadding)
|
||
|
]
|
||
|
public virtual int CellPadding {
|
||
|
get {
|
||
|
if (ControlStyleCreated == false) {
|
||
|
return -1;
|
||
|
}
|
||
|
return ((TableStyle)ControlStyle).CellPadding;
|
||
|
}
|
||
|
set {
|
||
|
((TableStyle)ControlStyle).CellPadding = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <devdoc>
|
||
|
/// <para>
|
||
|
/// Gets or sets
|
||
|
/// the spacing between each item.
|
||
|
/// </para>
|
||
|
/// </devdoc>
|
||
|
[
|
||
|
WebCategory("Layout"),
|
||
|
DefaultValue(-1),
|
||
|
WebSysDescription(SR.CheckBoxList_CellSpacing)
|
||
|
]
|
||
|
public virtual int CellSpacing {
|
||
|
get {
|
||
|
if (ControlStyleCreated == false) {
|
||
|
return -1;
|
||
|
}
|
||
|
return ((TableStyle)ControlStyle).CellSpacing;
|
||
|
}
|
||
|
set {
|
||
|
((TableStyle)ControlStyle).CellSpacing = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal override bool IsMultiSelectInternal {
|
||
|
get {
|
||
|
// a CheckBoxList is always multiselect.
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// <para>Indicates whether the control will be rendered when the data source has no items.</para>
|
||
|
/// </summary>
|
||
|
[DefaultValue(false)]
|
||
|
[Themeable(true)]
|
||
|
[WebCategory("Behavior")]
|
||
|
[WebSysDescription(SR.ListControl_RenderWhenDataEmpty)]
|
||
|
public virtual bool RenderWhenDataEmpty {
|
||
|
get {
|
||
|
object o = ViewState["RenderWhenDataEmpty"];
|
||
|
return ((o == null) ? false : (bool)o);
|
||
|
}
|
||
|
set {
|
||
|
ViewState["RenderWhenDataEmpty"] = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <devdoc>
|
||
|
/// <para>Gets or sets the number of columns to repeat.</para>
|
||
|
/// </devdoc>
|
||
|
[
|
||
|
WebCategory("Layout"),
|
||
|
DefaultValue(0),
|
||
|
WebSysDescription(SR.CheckBoxList_RepeatColumns)
|
||
|
]
|
||
|
public virtual int RepeatColumns {
|
||
|
get {
|
||
|
object o = ViewState["RepeatColumns"];
|
||
|
return((o == null) ? 0 : (int)o);
|
||
|
}
|
||
|
set {
|
||
|
if (value < 0) {
|
||
|
throw new ArgumentOutOfRangeException("value");
|
||
|
}
|
||
|
ViewState["RepeatColumns"] = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <devdoc>
|
||
|
/// <para>Gets or sets a value that indicates whether the control is displayed
|
||
|
/// vertically or horizontally.</para>
|
||
|
/// </devdoc>
|
||
|
[
|
||
|
WebCategory("Layout"),
|
||
|
DefaultValue(RepeatDirection.Vertical),
|
||
|
WebSysDescription(SR.Item_RepeatDirection)
|
||
|
]
|
||
|
public virtual RepeatDirection RepeatDirection {
|
||
|
get {
|
||
|
object o = ViewState["RepeatDirection"];
|
||
|
return((o == null) ? RepeatDirection.Vertical : (RepeatDirection)o);
|
||
|
}
|
||
|
set {
|
||
|
if (value < RepeatDirection.Horizontal || value > RepeatDirection.Vertical) {
|
||
|
throw new ArgumentOutOfRangeException("value");
|
||
|
}
|
||
|
ViewState["RepeatDirection"] = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <devdoc>
|
||
|
/// <para>Gets or sets a value that indicates whether the control is displayed in
|
||
|
/// <see langword='Table '/>or <see langword='Flow '/>layout.</para>
|
||
|
/// </devdoc>
|
||
|
[
|
||
|
WebCategory("Layout"),
|
||
|
DefaultValue(RepeatLayout.Table),
|
||
|
WebSysDescription(SR.WebControl_RepeatLayout)
|
||
|
]
|
||
|
public virtual RepeatLayout RepeatLayout {
|
||
|
get {
|
||
|
object o = ViewState["RepeatLayout"];
|
||
|
return((o == null) ? RepeatLayout.Table : (RepeatLayout)o);
|
||
|
}
|
||
|
set {
|
||
|
EnumerationRangeValidationUtil.ValidateRepeatLayout(value);
|
||
|
ViewState["RepeatLayout"] = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <devdoc>
|
||
|
/// <para>Gets or sets
|
||
|
/// the alignment of the text label associated with each checkbox.</para>
|
||
|
/// </devdoc>
|
||
|
[
|
||
|
WebCategory("Appearance"),
|
||
|
DefaultValue(TextAlign.Right),
|
||
|
WebSysDescription(SR.WebControl_TextAlign)
|
||
|
]
|
||
|
public virtual TextAlign TextAlign {
|
||
|
get {
|
||
|
object align = ViewState["TextAlign"];
|
||
|
return((align == null) ? TextAlign.Right : (TextAlign)align);
|
||
|
}
|
||
|
set {
|
||
|
if (value < TextAlign.Left || value > TextAlign.Right) {
|
||
|
throw new ArgumentOutOfRangeException("value");
|
||
|
}
|
||
|
ViewState["TextAlign"] = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// <para>Creates a new control style object.</para>
|
||
|
/// </devdoc>
|
||
|
protected override Style CreateControlStyle() {
|
||
|
return new TableStyle(ViewState);
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// <para>Catches post data for each <see cref='System.Web.UI.WebControls.CheckBox'/> in the list.</para>
|
||
|
/// </devdoc>
|
||
|
protected override Control FindControl(string id, int pathOffset) {
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
protected internal override void OnInit(EventArgs e) {
|
||
|
base.OnInit(e);
|
||
|
|
||
|
if (!DesignMode && !String.IsNullOrEmpty(ItemType)) {
|
||
|
DataBoundControlHelper.EnableDynamicData(this, ItemType);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// <para>Configures the <see cref='System.Web.UI.WebControls.CheckBoxList'/> prior to rendering on the client.</para>
|
||
|
/// </devdoc>
|
||
|
protected internal override void OnPreRender(EventArgs e) {
|
||
|
base.OnPreRender(e);
|
||
|
_controlToRepeat.AutoPostBack = AutoPostBack;
|
||
|
_controlToRepeat.CausesValidation = CausesValidation;
|
||
|
_controlToRepeat.ValidationGroup = ValidationGroup;
|
||
|
|
||
|
if (Page != null) {
|
||
|
// ensure postback data for those checkboxes which get unchecked or are different from their default value
|
||
|
for (int i=0; i < Items.Count; i++) {
|
||
|
SetControlToRepeatID(this, _controlToRepeat, i);
|
||
|
Page.RegisterRequiresPostBack(_controlToRepeat);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// <para>
|
||
|
/// Displays the <see cref='System.Web.UI.WebControls.CheckBoxList'/> on the client.
|
||
|
/// </para>
|
||
|
/// </devdoc>
|
||
|
protected internal override void Render(HtmlTextWriter writer) {
|
||
|
// Rendering an empty table is not valid xhtml or html 4, so throw
|
||
|
if (RepeatLayout == RepeatLayout.Table && RenderWhenDataEmpty) {
|
||
|
throw new InvalidOperationException(SR.GetString(SR.ListControl_RenderWhenDataEmptyNotSupportedWithTableLayout, ID));
|
||
|
}
|
||
|
|
||
|
// Don't render anything if the control is empty (unless the developer opts in by setting RenderWhenDataEmpty).
|
||
|
// empty table is not xhtml compliant.
|
||
|
if (Items.Count == 0 && !EnableLegacyRendering && !RenderWhenDataEmpty) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
RepeatInfo repeatInfo = new RepeatInfo();
|
||
|
Style style = (ControlStyleCreated ? ControlStyle : null);
|
||
|
short tabIndex = TabIndex;
|
||
|
bool undirtyTabIndex = false;
|
||
|
|
||
|
// TabIndex here is special... it needs to be applied to the individual
|
||
|
// checkboxes and not the outer control itself
|
||
|
|
||
|
// Set the TextAlign property.
|
||
|
_controlToRepeat.TextAlign = TextAlign;
|
||
|
|
||
|
// cache away the TabIndex property state
|
||
|
_controlToRepeat.TabIndex = tabIndex;
|
||
|
if (tabIndex != 0) {
|
||
|
if (ViewState.IsItemDirty("TabIndex") == false) {
|
||
|
undirtyTabIndex = true;
|
||
|
}
|
||
|
TabIndex = 0;
|
||
|
}
|
||
|
|
||
|
repeatInfo.RepeatColumns = RepeatColumns;
|
||
|
repeatInfo.RepeatDirection = RepeatDirection;
|
||
|
|
||
|
// If the device does not support tables, use the flow layout to render
|
||
|
if (!DesignMode && !Context.Request.Browser.Tables) {
|
||
|
repeatInfo.RepeatLayout = RepeatLayout.Flow;
|
||
|
}
|
||
|
else {
|
||
|
repeatInfo.RepeatLayout = RepeatLayout;
|
||
|
}
|
||
|
|
||
|
if (repeatInfo.RepeatLayout == RepeatLayout.Flow) {
|
||
|
repeatInfo.EnableLegacyRendering = EnableLegacyRendering;
|
||
|
}
|
||
|
|
||
|
// VSWhidbey 373655
|
||
|
// Temporarily suppress AccessKey so base does not render it on the outside tag
|
||
|
_oldAccessKey = AccessKey;
|
||
|
AccessKey = String.Empty;
|
||
|
|
||
|
repeatInfo.RenderRepeater(writer, (IRepeatInfoUser)this, style, this);
|
||
|
|
||
|
// restore the state of AccessKey property
|
||
|
AccessKey = _oldAccessKey;
|
||
|
|
||
|
// restore the state of the TabIndex property
|
||
|
if (tabIndex != 0) {
|
||
|
TabIndex = tabIndex;
|
||
|
}
|
||
|
if (undirtyTabIndex) {
|
||
|
ViewState.SetItemDirty("TabIndex", false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// <para>Processes posted data for the <see cref='System.Web.UI.WebControls.CheckBoxList'/> control.</para>
|
||
|
/// </devdoc>
|
||
|
bool IPostBackDataHandler.LoadPostData(String postDataKey, NameValueCollection postCollection) {
|
||
|
return LoadPostData(postDataKey, postCollection);
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// <para>Processes posted data for the <see cref='System.Web.UI.WebControls.CheckBoxList'/> control.</para>
|
||
|
/// </devdoc>
|
||
|
protected virtual bool LoadPostData(String postDataKey, NameValueCollection postCollection) {
|
||
|
if (IsEnabled == false) {
|
||
|
// When a CheckBoxList is disabled, then there is no postback
|
||
|
// data for it. Any checked state information has been loaded
|
||
|
// via view state.
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// postDataKey looks like one of two things:
|
||
|
// 1. "<uniqueid>$<index>" (EffectiveClientIDMode != Static when rendered last request)
|
||
|
// 2. "<uniqueid>$<id>_<index>" (EffectiveClientIDMode == Static last request)
|
||
|
|
||
|
string strIndex = postDataKey.Substring(UniqueID.Length + 1);
|
||
|
// strIndex is now either (1) "<index>" or (2) "<id>_<index>"
|
||
|
// Detect case #2 by looking for an underscore. Use LastIndexOf in case <id> contains one too.
|
||
|
// No need to worry about a case that looks like "<uniqueid>$<id>", it would never have been rendered that way.
|
||
|
int underscoreIndex = strIndex.LastIndexOf('_');
|
||
|
if (underscoreIndex != -1) {
|
||
|
strIndex = strIndex.Substring(underscoreIndex + 1);
|
||
|
}
|
||
|
// strIndex is now definitely the index as a string, regardless of which case postDataKey was in.
|
||
|
int index = Int32.Parse(strIndex, CultureInfo.InvariantCulture);
|
||
|
|
||
|
EnsureDataBoundInLoadPostData();
|
||
|
|
||
|
// Maintain state from the form
|
||
|
if (index >= 0 && index < Items.Count) {
|
||
|
ListItem item = Items[index];
|
||
|
if (item.Enabled == false) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool newCheckState = (postCollection[postDataKey] != null);
|
||
|
|
||
|
if (item.Selected != newCheckState) {
|
||
|
item.Selected = newCheckState;
|
||
|
// LoadPostData will be invoked for each CheckBox that changed
|
||
|
// Suppress multiple change notification and fire only ONE change event
|
||
|
if (!_hasNotifiedOfChange) {
|
||
|
_hasNotifiedOfChange = true;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// <para>Raises when posted data for a control has changed.</para>
|
||
|
/// </devdoc>
|
||
|
void IPostBackDataHandler.RaisePostDataChangedEvent() {
|
||
|
RaisePostDataChangedEvent();
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// <para>Raises when posted data for a control has changed.</para>
|
||
|
/// </devdoc>
|
||
|
protected virtual void RaisePostDataChangedEvent() {
|
||
|
if (AutoPostBack && !Page.IsPostBackEventControlRegistered) {
|
||
|
// VSWhidbey 204824
|
||
|
Page.AutoPostBackControl = this;
|
||
|
|
||
|
if (CausesValidation) {
|
||
|
Page.Validate(ValidationGroup);
|
||
|
}
|
||
|
}
|
||
|
OnSelectedIndexChanged(EventArgs.Empty);
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// </devdoc>
|
||
|
bool IRepeatInfoUser.HasFooter {
|
||
|
get {
|
||
|
return HasFooter;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// </devdoc>
|
||
|
protected virtual bool HasFooter {
|
||
|
get {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// </devdoc>
|
||
|
bool IRepeatInfoUser.HasHeader {
|
||
|
get {
|
||
|
return HasHeader;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// </devdoc>
|
||
|
protected virtual bool HasHeader {
|
||
|
get {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// </devdoc>
|
||
|
bool IRepeatInfoUser.HasSeparators {
|
||
|
get {
|
||
|
return HasSeparators;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// </devdoc>
|
||
|
protected virtual bool HasSeparators {
|
||
|
get {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// </devdoc>
|
||
|
int IRepeatInfoUser.RepeatedItemCount {
|
||
|
get {
|
||
|
return RepeatedItemCount;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// </devdoc>
|
||
|
protected virtual int RepeatedItemCount {
|
||
|
get {
|
||
|
return (Items != null) ? Items.Count : 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// </devdoc>
|
||
|
Style IRepeatInfoUser.GetItemStyle(ListItemType itemType, int repeatIndex) {
|
||
|
return GetItemStyle(itemType, repeatIndex);
|
||
|
}
|
||
|
|
||
|
|
||
|
protected virtual Style GetItemStyle(ListItemType itemType, int repeatIndex) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// Called by the RepeatInfo helper to render each item
|
||
|
/// </devdoc>
|
||
|
void IRepeatInfoUser.RenderItem(ListItemType itemType, int repeatIndex, RepeatInfo repeatInfo, HtmlTextWriter writer) {
|
||
|
RenderItem(itemType, repeatIndex, repeatInfo, writer);
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// Called by the RepeatInfo helper to render each item
|
||
|
/// </devdoc>
|
||
|
protected virtual void RenderItem(ListItemType itemType, int repeatIndex, RepeatInfo repeatInfo, HtmlTextWriter writer) {
|
||
|
if (repeatIndex == 0) {
|
||
|
_cachedIsEnabled = IsEnabled;
|
||
|
_cachedRegisterEnabled = (Page != null) && IsEnabled && (SaveSelectedIndicesViewState == false);
|
||
|
}
|
||
|
|
||
|
int repeatIndexOffset = repeatIndex;
|
||
|
|
||
|
ListItem item = Items[repeatIndexOffset];
|
||
|
|
||
|
// VSWhidbey 403433 Render expando attributes.
|
||
|
_controlToRepeat.Attributes.Clear();
|
||
|
if (item.HasAttributes) {
|
||
|
foreach (string key in item.Attributes.Keys) {
|
||
|
_controlToRepeat.Attributes[key] = item.Attributes[key];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Dev10 684108: reset the CssClass for each item.
|
||
|
if (!string.IsNullOrEmpty(_controlToRepeat.CssClass)) {
|
||
|
_controlToRepeat.CssClass = "";
|
||
|
}
|
||
|
|
||
|
if (RenderingCompatibility >= VersionUtil.Framework40) {
|
||
|
_controlToRepeat.InputAttributes.Add("value", item.Value);
|
||
|
}
|
||
|
SetControlToRepeatID(this, _controlToRepeat, repeatIndexOffset);
|
||
|
_controlToRepeat.Text = item.Text;
|
||
|
_controlToRepeat.Checked = item.Selected;
|
||
|
_controlToRepeat.Enabled = _cachedIsEnabled && item.Enabled;
|
||
|
_controlToRepeat.AccessKey = _oldAccessKey;
|
||
|
|
||
|
if (_cachedRegisterEnabled && _controlToRepeat.Enabled) {
|
||
|
// 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 SelectedIndices is not saved in view state
|
||
|
Page.RegisterEnabledControl(_controlToRepeat);
|
||
|
}
|
||
|
|
||
|
_controlToRepeat.RenderControl(writer);
|
||
|
}
|
||
|
}
|
||
|
}
|