我有一个DropDownList
控件,它实际上并不想启用ViewState,因为服务器上可以随时提供项目列表。
在这种情况下,回发后显然会显示一个空的Items
列表,但同时我希望触发SelectedIndexChanged
事件(使用AutoPostBack)
DropDownList
控件实际上是作为复合控件的一部分动态创建的,复合控件创建了任意数量的DDL - 其中一些项源自ASPX文件(复合控件内容),其他项目绑定到数据源。我希望这种复杂性不是问题的根源。
我的想法是我应该将列表数据绑定到初始PageLoad上的Items
集合,并在PostBacks上重新绑定 late ,但我不确定何时。如果在PostBack上再次出现空SelectedIndexChanged
列表,我也不确定是否会调用Items
事件处理程序。
因为它是CompositeControl的一部分,我想知道是否应该在Control State中存储一些额外的信息。是否可以在Conposite Control Save/LoadControlState
,Save/LoadViewState
,RaisePostDataChangedEvent
或RaisePostbackEvent
中实施某些内容?
也许我应该在PostBack早期再次重新绑定Item
列表,例如在PageLoad之前?
任何指针都将受到赞赏。
由于
下面提供的代码(删除大部分与问题无关的内容)
页面ASPX 请注意,我想动态填充{Group,Location,Division和BusinessUnit} DropDownLists的ListItems,而不将项目转储到ViewState,但{Enabled} DDL是下面定义的静态项目。
<cc1:FilterBar ID="FilterBar1" runat="server" CssClass="filterbar" OnFilterChanged="OnFilterChanged">
<cc1:Filter ID="Group" Label="Group" Type="DropDownList" />
<cc1:Filter ID="Location" Label="[Location]" Type="DropDownList" />
<cc1:Filter ID="Division" Label="[Division]" Type="DropDownList" />
<cc1:Filter ID="BusinessUnit" Label="[Business Unit]" Type="DropDownList" />
<cc1:Filter ID="Enabled" Label="Users" Type="DropDownList">
<asp:ListItem Value="X" Text="All"></asp:ListItem>
<asp:ListItem Value="1" Text="Active Only"></asp:ListItem>
<asp:ListItem Value="0" Text="Archived Only"></asp:ListItem>
</cc1:Filter>
</cc1:FilterBar>
页面代码背后
protected void Page_Load(object sender, EventArgs e)
{
//if (!Page.IsPostBack) // whether to restire BindFilterBar() to first load or not?
BindFilterBar();
}
protected void BindFilterBar()
{
var h = new FilterBarBindHelper(FilterBar1);
h.AutoBindLocation(Filter.UserLocation.ID);
h.AutoBindDivision(Filter.UserDivision.ID);
h.AutoBindBusinessUnit(Filter.UserBusinessUnit.ID);
h.AutoBindGroup(Filter.UserGroup.ID);
var filter = FilterBar1.GetFilterByID("Enabled");
filter.SelectedValue = Filter.UserEnabledAsChar.ToString();
}
protected void OnFilterChanged(object sender, FilterChangedEventArgs e)
{
Debug.WriteLine($"[CompletionResults.aspx] OnFilterChanged(e={{FilterID={e.FilterID}; NewValue={e.NewValue} NewText={e.NewText}}})");
switch(e.FilterID)
{
case "Enabled":
Filter.UserEnabledAsChar = e.NewValue;
break;
case "Group":
if (e.NewValueAsInt == null)
Filter.UserGroup.Reset();
else
Filter.UserGroup.Set((int)e.NewValueAsInt, e.NewText);
break;
case "Division":
if (e.NewValueAsInt == null)
Filter.UserDivision.Reset();
else
Filter.UserDivision.Set((int)e.NewValueAsInt, e.NewText);
break;
case "BusinessUnit":
if (e.NewValueAsInt == null)
Filter.UserBusinessUnit.Reset();
else
Filter.UserBusinessUnit.Set((int)e.NewValueAsInt, e.NewText);
break;
case "Location":
if (e.NewValueAsInt == null)
Filter.UserLocation.Reset();
else
Filter.UserLocation.Set((int)e.NewValueAsInt, e.NewText);
break;
default:
return;
}
// Update Selection Text
BindSelectionText();
// Update the Grid
BindGrid();
}
/// <summary>
/// User has selected a new Page number from the UserGrid
/// </summary>
protected void OnPageChanged(object sender, EventArgs args)
{
Filter.ShowAllRows = false;
Filter.PageIndex = UserGrid.PageIndex;
BindGrid();
}
protected void OnAction(object sender, ToolbarActionClickedEventArgs args)
{
if (args.ActionValue == "email")
OnEmail();
}
FilterBarBindHelper.cs
public class FilterBarBindHelper
{
private readonly FilterBar _filterBar;
private readonly OrganisationProperties _orgProps;
public FilterBarBindHelper(FilterBar filterBar)
{
if (filterBar == null)
throw new ArgumentNullException(nameof(filterBar));
_filterBar = filterBar;
_orgProps = ObjectCache.GetOrganisationProperties();
}
/// <summary>
/// Binds Group list data to Filter, optionally creating the Filter if it doesn't exist
/// </summary>
/// <param name="selectedID">ID of currently selected Group to filter by (0 or null if none)</param>
/// <param name="createFilter">Create the Filter if not found</param>
public void AutoBindGroup(int? selectedID, bool createFilter = true)
{
// All Organisations have Groups.
// Only populate if a FilterBar Filter with ID="Groups" can be found or if createFilter = true
var f = _filterBar.GetFilterByID("Group");
if (f == null)
if (createFilter)
f = CreateFilter("Group", "Group");
else
return;
// Populate Filter list with items
var src = GroupCache.Get();
f.Items.Clear();
f.Items.Add(Any());
f.Items.AddRange(src.Select(s => new ListItem() { Value = s.StringID, Text = s.Name }).ToArray());
// Assign localised name
f.Label = _orgProps.GroupNm;
// Select
if((selectedID ?? 0) != 0)
f.SelectedValue = selectedID.Value.ToString();
_filterBar.RefreshFilter(f);
}
/// <summary>
/// Create a FilterBar Filter and add it to the FilterBar
/// </summary>
/// <param name="filterID">ID of filter to add</param>
/// <param name="label">Filter's label</param>
/// <param name="type">Filter's type</param>
/// <returns>New Filter</returns>
private Filter CreateFilter(string filterID, string label, FilterType type = FilterType.DropDownList)
{
var f = new Filter() { ID = filterID, Type = type, Label = label };
_filterBar.Filters.Add(f);
return f;
}
private ListItem Any()
{
return new ListItem("Any", "X");
}
}
Toolbar.cs
[
AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal),
AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal),
DefaultProperty("Items"),
ParseChildren(true, "Items"),
ToolboxData("<{0}:Toolbar runat=server />"),
Designer(typeof(ToolbarDesigner))
]
public class Toolbar : CompositeControl
{
private string _preText = "Actions: ";
private bool _showRight = true;
private readonly List<ToolbarItem> _list = new List<ToolbarItem>();
public Toolbar()
{
PreText = "Actions: ";
ObjectNameSingle = "Item";
ObjectNamePlural = "Items";
ShowRight = true;
}
protected override HtmlTextWriterTag TagKey { get { return HtmlTextWriterTag.Div; } }
[
Category("Behavior"),
Description("The Item collection"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
Editor(typeof(ToolbarCollectionEditor), typeof(UITypeEditor)),
PersistenceMode(PersistenceMode.InnerDefaultProperty)
]
public List<ToolbarItem> Items
{
get { return _list; }
}
//various properties (snip)
protected override void RenderContents(HtmlTextWriter writer)
{
if(_leftSide != null)
_leftSide.RenderControl(writer);
if(_rightSide != null)
_rightSide.RenderControl(writer);
if(_leftSide == null && _rightSide == null && DesignMode)
writer.Write("No Toolbar Items have been set up in designer");
}
public void RebuildChildControls()
{
// Indicate that the child controls need rebuilding
// Ref: http://yourdotnetdesignteam.blogspot.co.uk/2008/09/life-cycle-of-aspnet-controls_28.html "Composite Controls" section:
// "Note 1: When the Composite Control is displayed normally, 'CreateChildControls' is called AFTER the 'Load' method.
// However, when a post back event occurs, 'CreateChildControls' is called AFTER the 'Init' method.
// This certainly makes the 'CreateChildControls' method an "interesting" aspect of the Life Cycle."
// So on postback, the calling method should call RebuildChildControls() after repopulating/modifying Filter Items
ChildControlsCreated = false;
//RecreateChildControls(); this will happen in due course anyway
}
// Controls in Composite Control
private ToolbarActionContainer _leftSide;
private WebControl _rightSide;
private ToolbarActionItem _liPreText;
private Literal _litPreText;
private HtmlGenericControl _spanRowCount;
private HtmlGenericControl _spanPageCount;
private DropDownList _ddlPage;
private Literal _litPageCount;
protected override void CreateChildControls()
{
Controls.Clear();
_leftSide = new ToolbarActionContainer(); // UL
_rightSide = new WebControl(HtmlTextWriterTag.Div);
_liPreText = new ToolbarActionItem(ToolbarActionType.PreText);
_litPreText = new Literal() { Text = "Actions: " };
// Create the Toolbar Action Items
var actionItems = new List<ToolbarActionItem>(_list.Count);
foreach (var item in _list)
{
if (item.Visible)
{
var li = CreateToolbarActionItemControls(item);
actionItems.Add(li);
}
}
_spanRowCount = new HtmlGenericControl("span"); // "NNN entitie(s) found";
_spanPageCount = new HtmlGenericControl("span"); // "Page [DDL] of [LIT]";
_ddlPage = new DropDownList() { ID = "PageDDL", AutoPostBack = true, EnableViewState = false };
_litPageCount = new Literal();
_spanPageCount.Controls.Add(new Literal() { Text = "Page " });
_spanPageCount.Controls.Add(_ddlPage);
_spanPageCount.Controls.Add(new Literal() { Text = " of " });
_spanPageCount.Controls.Add(_litPageCount);
_liPreText.Controls.Add(_litPreText);
_leftSide.Controls.Add(_liPreText);
foreach(var item in actionItems)
_leftSide.Controls.Add(item);
_rightSide.Controls.Add(_spanRowCount);
_rightSide.Controls.Add(_spanPageCount);
Controls.Add(_leftSide);
Controls.Add(_rightSide);
_ddlPage.SelectedIndexChanged += PageDDL_SelectedIndexChanged;
base.CreateChildControls();
ChildControlsCreated = true;
UpdateRowCountText(); // Assigns the RowCount text to the span control _spanPageCount
UpdatePageCountText(); // The Y in "Page X of Y"
ApplyStandardClassNames();
CreatePageDDLItems(); // Populates Items in PageDDL (if required)
AssignPageDDLSelectedValue(); // Assigns the current PageDDL value (if required)
}
protected virtual ToolbarActionItem CreateToolbarActionItemControls(ToolbarItem item)
{
// Create List Item and add CssClass if required
var li = CreateToolbarActionItem(ToolbarActionType.Action);
if (item.HasCssClass)
li.CssClass = item.CssClass;
if (item.TextOnly)
{
// Text Only Action Items are added as literal text
li.Controls.Add(new Literal() { Text = item.Text });
}
else
{
var btn = new LinkButton() { Text = item.Text, ToolTip = item.ToolTip, CssClass = item.CssClass };
if (item.HasURLLink)
btn.Attributes.Add("href", item.URLLink);
else
{
btn.Click += LinkButton_Clicked;
_map.Add(btn, item); // used if posting back
// btn.Attributes.Add("href", "#");
// var js = MakeButtonJavaScript(item);
// if (js != string.Empty)
// btn.Attributes.Add("onclick", "javascript:" + js);
if(!string.IsNullOrEmpty(item.PrePostbackJSFunction))
btn.OnClientClick = $"return {item.PrePostbackJSFunction}(this);";
}
li.Controls.Add(btn);
}
return li;
}
protected virtual ToolbarActionItem CreateToolbarActionItem(ToolbarActionType type)
{
return new ToolbarActionItem(type);
}
private void CreatePageDDLItems()
{
// No DDL control created yet?
if (!ChildControlsCreated)
return;
var pageCount = PageCount;
// Current count is already correct?
if (pageCount == _ddlPage.Items.Count - 1) // -1 = "all"
return;
_ddlPage.Items.Clear();
_spanPageCount.Visible = (pageCount > 1); // Show this section only if multipage
// Not multi page?
if (pageCount <= 1)
return;
var li = new ListItem("All", "all");
_ddlPage.Items.Add(li);
for (var i = 0; i < pageCount; i++)
{
li = new ListItem((i + 1).ToString());
_ddlPage.Items.Add(li);
}
// AssignPageDDLSelectedValue() is already called whenever CreatePageDDLItems() is called!
//AssignPageDDLSelectedValue();
}
private void AssignPageDDLSelectedValue()
{
if (!ChildControlsCreated)
return;
if (ShowAllPages)
_ddlPage.SelectedValue = "all";
else
_ddlPage.SelectedValue = CurrentPage.ToString();
}
private void LinkButton_Clicked(object sender, EventArgs e)
{
System.Diagnostics.Debug.Assert(sender is LinkButton);
var button = sender as LinkButton;
var item = _map[button];
// Raise event with item's value
OnActionButtonClicked(item.Value);
}
private void PageDDL_SelectedIndexChanged(object sender, System.EventArgs e)
{
System.Diagnostics.Debug.Assert(sender == _ddlPage);
var v = _ddlPage.SelectedValue;
var newShowAllPages = (v == "all");
var newPage = newShowAllPages ? 0 : int.Parse(v);
// No Change?
if (newShowAllPages == ShowAllPages &&
(!newShowAllPages && (newPage == CurrentPage)))
return;
// Assign and raise event
ShowAllPages = newShowAllPages;
CurrentPage = newPage;
OnPageChanged(newShowAllPages, newPage);
}
private void OnActionButtonClicked(string id)
{
var evt = ActionClicked;
// check there are subscribers, and trigger if necessary
if (evt != null)
evt(this, new ToolbarActionClickedEventArgs(id));
}
private void OnPageChanged(bool showAll, int newPageNumber)
{
var evt = PageChanged;
// check there are subscribers, and trigger if necessary
if (evt != null)
evt(this, new ToolbarPageChangedEventArgs(showAll, newPageNumber));
}
}
public class ToolbarPageChangedEventArgs : EventArgs
{
public bool ShowAllPages { get; }
public int NewPageNumber { get; }
internal ToolbarPageChangedEventArgs(bool showAllPages, int newPageNumber)
{
ShowAllPages = showAllPages;
NewPageNumber = newPageNumber;
}
}
public class ToolbarActionClickedEventArgs : EventArgs
{
public string ActionValue { get; }
internal ToolbarActionClickedEventArgs(string value)
{
ActionValue = value;
}
}
public delegate void ToolbarPageChangedEventHandler(object sender, ToolbarPageChangedEventArgs args);
public delegate void ToolbarActionClickedEventHandler(object sender, ToolbarActionClickedEventArgs args);
ToolbarItem.cs
[TypeConverter(typeof(ExpandableObjectConverter))]
public class ToolbarItem
{
public ToolbarItem() : this(string.Empty, string.Empty, string.Empty, string.Empty)
{
}
public ToolbarItem(string text, string value, string jslink, string urllink)
{
Text = text;
Value = value;
JSLink = jslink;
URLLink = urllink;
CssClass = string.Empty;
ToolTip = string.Empty;
PrePostbackJSFunction = string.Empty;
Visible = true;
}
.... snip ...
}
public enum ToolbarActionType
{
None = 0,
PreText = 1,
Action = 2,
}