1 //------------------------------------------------------------------------------ 2 // <copyright file="HtmlSelect.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 //------------------------------------------------------------------------------ 6 7 namespace System.Web.UI.HtmlControls { 8 using System.Runtime.Serialization.Formatters; 9 using System.Text; 10 using System.ComponentModel; 11 using System; 12 using System.Collections; 13 using System.Collections.Specialized; 14 using System.Data; 15 using System.Web; 16 using System.Web.Util; 17 using System.Web.UI; 18 using System.Web.UI.WebControls; 19 using System.Globalization; 20 using Debug=System.Web.Util.Debug; 21 using System.Security.Permissions; 22 23 public class HtmlSelectBuilder : ControlBuilder { 24 25 GetChildControlType(string tagName, IDictionary attribs)26 public override Type GetChildControlType(string tagName, IDictionary attribs) { 27 if (StringUtil.EqualsIgnoreCase(tagName, "option")) 28 return typeof(ListItem); 29 30 return null; 31 } 32 33 AllowWhitespaceLiterals()34 public override bool AllowWhitespaceLiterals() { 35 return false; 36 } 37 } 38 39 40 /// <devdoc> 41 /// <para> 42 /// The <see langword='HtmlSelect'/> 43 /// class defines the methods, properties, and events for the 44 /// HtmlSelect control. This class allows programmatic access to the HTML 45 /// <select> element on the server. 46 /// </para> 47 /// </devdoc> 48 [ 49 DefaultEvent("ServerChange"), 50 ValidationProperty("Value"), 51 ControlBuilderAttribute(typeof(HtmlSelectBuilder)), 52 SupportsEventValidation, 53 ] 54 public class HtmlSelect : HtmlContainerControl, IPostBackDataHandler, IParserAccessor { 55 56 private static readonly object EventServerChange = new object(); 57 58 internal const string DataBoundViewStateKey = "_!DataBound"; 59 60 private object dataSource; 61 private ListItemCollection items; 62 private int cachedSelectedIndex; 63 64 private bool _requiresDataBinding; 65 private bool _inited; 66 private bool _throwOnDataPropertyChange; 67 68 private DataSourceView _currentView; 69 private bool _currentViewIsFromDataSourceID; 70 private bool _currentViewValid; 71 private bool _pagePreLoadFired; 72 73 /* 74 * Creates an intrinsic Html SELECT control. 75 */ 76 HtmlSelect()77 public HtmlSelect() : base("select") { 78 cachedSelectedIndex = -1; 79 } 80 81 82 /// <devdoc> 83 /// <para>[To be supplied.]</para> 84 /// </devdoc> 85 [ 86 DefaultValue(""), 87 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), 88 WebCategory("Data"), 89 WebSysDescription(SR.HtmlSelect_DataMember) 90 ] 91 public virtual string DataMember { 92 get { 93 object o = ViewState["DataMember"]; 94 if (o != null) 95 return (string)o; 96 return String.Empty; 97 } 98 set { 99 Attributes["DataMember"] = MapStringAttributeToString(value); 100 OnDataPropertyChanged(); 101 } 102 } 103 104 105 /// <devdoc> 106 /// Gets or sets the data source to databind the list values 107 /// in the <see langword='HtmlSelect'/> control against. This provides data to 108 /// populate the select list with items. 109 /// </devdoc> 110 [ 111 WebCategory("Data"), 112 DefaultValue(null), 113 WebSysDescription(SR.BaseDataBoundControl_DataSource), 114 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) 115 ] 116 public virtual object DataSource { 117 get { 118 return dataSource; 119 } 120 set { 121 if ((value == null) || (value is IListSource) || (value is IEnumerable)) { 122 dataSource = value; 123 OnDataPropertyChanged(); 124 } 125 else { 126 throw new ArgumentException(SR.GetString(SR.Invalid_DataSource_Type, ID)); 127 } 128 } 129 } 130 131 132 /// <summary> 133 /// The ID of the DataControl that this control should use to retrieve 134 /// its data source. When the control is bound to a DataControl, it 135 /// can retrieve a data source instance on-demand, and thereby attempt 136 /// to work in auto-DataBind mode. 137 /// </summary> 138 [ 139 DefaultValue(""), 140 WebCategory("Data"), 141 WebSysDescription(SR.BaseDataBoundControl_DataSourceID), 142 ] 143 public virtual string DataSourceID { 144 get { 145 object o = ViewState["DataSourceID"]; 146 if (o != null) { 147 return (string)o; 148 } 149 return String.Empty; 150 } 151 set { 152 ViewState["DataSourceID"] = value; 153 OnDataPropertyChanged(); 154 } 155 } 156 157 158 /// <devdoc> 159 /// <para> 160 /// Gets or sets the field in the data source that provides 161 /// the text for an option entry in the HtmlSelect control. 162 /// </para> 163 /// </devdoc> 164 [ 165 WebCategory("Data"), 166 DefaultValue(""), 167 WebSysDescription(SR.HtmlSelect_DataTextField) 168 ] 169 public virtual string DataTextField { 170 get { 171 string s = Attributes["DataTextField"]; 172 return((s == null) ? String.Empty : s); 173 } 174 set { 175 Attributes["DataTextField"] = MapStringAttributeToString(value); 176 if (_inited) { 177 RequiresDataBinding = true; 178 } 179 } 180 } 181 182 183 /// <devdoc> 184 /// <para> 185 /// Gets or sets the field in the data source that provides 186 /// the option item value for the <see langword='HtmlSelect'/> 187 /// control. 188 /// </para> 189 /// </devdoc> 190 [ 191 WebCategory("Data"), 192 DefaultValue(""), 193 WebSysDescription(SR.HtmlSelect_DataValueField) 194 ] 195 public virtual string DataValueField { 196 get { 197 string s = Attributes["DataValueField"]; 198 return((s == null) ? String.Empty : s); 199 } 200 set { 201 Attributes["DataValueField"] = MapStringAttributeToString(value); 202 if (_inited) { 203 RequiresDataBinding = true; 204 } 205 } 206 } 207 208 209 /// <devdoc> 210 /// <para>[To be supplied.]</para> 211 /// </devdoc> 212 public override string InnerHtml { 213 get { 214 throw new NotSupportedException(SR.GetString(SR.InnerHtml_not_supported, this.GetType().Name)); 215 } 216 set { 217 throw new NotSupportedException(SR.GetString(SR.InnerHtml_not_supported, this.GetType().Name)); 218 } 219 } 220 221 222 /// <devdoc> 223 /// <para>[To be supplied.]</para> 224 /// </devdoc> 225 public override string InnerText { 226 get { 227 throw new NotSupportedException(SR.GetString(SR.InnerText_not_supported, this.GetType().Name)); 228 } 229 set { 230 throw new NotSupportedException(SR.GetString(SR.InnerText_not_supported, this.GetType().Name)); 231 } 232 } 233 234 235 protected bool IsBoundUsingDataSourceID { 236 get { 237 return (DataSourceID.Length > 0); 238 } 239 } 240 241 /* 242 * A collection containing the list of items. 243 */ 244 245 /// <devdoc> 246 /// <para> 247 /// Gets the list of option items in an <see langword='HtmlSelect'/> control. 248 /// </para> 249 /// </devdoc> 250 [ 251 Browsable(false), 252 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), 253 ] 254 public ListItemCollection Items { 255 get { 256 if (items == null) { 257 items = new ListItemCollection(); 258 if (IsTrackingViewState) 259 ((IStateManager)items).TrackViewState(); 260 } 261 return items; 262 } 263 } 264 265 /* 266 * Multi-select property. 267 */ 268 269 /// <devdoc> 270 /// <para> 271 /// Gets or sets a value indicating whether multiple option items can be selected 272 /// from the list. 273 /// </para> 274 /// </devdoc> 275 [ 276 WebCategory("Behavior"), 277 DefaultValue(""), 278 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) 279 ] 280 public bool Multiple { 281 get { 282 string s = Attributes["multiple"]; 283 return((s != null) ? (s.Equals("multiple")) : false); 284 } 285 286 set { 287 if (value) 288 Attributes["multiple"] = "multiple"; 289 else 290 Attributes["multiple"] = null; 291 } 292 } 293 294 /* 295 * Name property. 296 */ 297 298 [ 299 WebCategory("Behavior"), 300 DefaultValue(""), 301 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) 302 ] 303 public string Name { 304 get { 305 return UniqueID; 306 //string s = Attributes["name"]; 307 //return ((s != null) ? s : ""); 308 } 309 set { 310 //Attributes["name"] = MapStringAttributeToString(value); 311 } 312 } 313 314 // Value that gets rendered for the Name attribute 315 internal string RenderedNameAttribute { 316 get { 317 return Name; 318 //string name = Name; 319 //if (name.Length == 0) 320 // return UniqueID; 321 322 //return name; 323 } 324 } 325 326 327 protected bool RequiresDataBinding { 328 get { 329 return _requiresDataBinding; 330 } 331 set { 332 _requiresDataBinding = value; 333 } 334 } 335 336 /* 337 * The index of the selected item. 338 * Returns the first selected item if list is multi-select. 339 * Returns -1 if there is no selected item. 340 */ 341 342 /// <devdoc> 343 /// <para> 344 /// Gets or sets the ordinal index of the selected option item in an 345 /// <see langword='HtmlSelect'/> control. If multiple items are selected, this 346 /// property holds the index of the first item selected in the list. 347 /// </para> 348 /// </devdoc> 349 [ 350 Browsable(false), 351 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), 352 HtmlControlPersistable(false), 353 ] 354 public virtual int SelectedIndex { 355 get { 356 for (int i=0; i < Items.Count; i++) { 357 if (Items[i].Selected) 358 return i; 359 } 360 if (Size <= 1 && !Multiple) { 361 // SELECT as a dropdown must have a selection 362 if (Items.Count > 0) 363 Items[0].Selected = true; 364 return 0; 365 } 366 return -1; 367 } 368 set { 369 // if we have no items, save the selectedindex 370 // for later databinding 371 if (Items.Count == 0) { 372 cachedSelectedIndex = value; 373 } 374 else { 375 if (value < -1 || value >= Items.Count) { 376 throw new ArgumentOutOfRangeException("value"); 377 } 378 ClearSelection(); 379 if (value >= 0) 380 Items[value].Selected = true; 381 } 382 } 383 } 384 385 /* 386 * SelectedIndices property. 387 * Protected property for getting array of selected indices. 388 */ 389 390 /// <internalonly/> 391 /// <devdoc> 392 /// </devdoc> 393 protected virtual int[] SelectedIndices { 394 get { 395 int n = 0; 396 int[] temp = new int[3]; 397 for (int i=0; i < Items.Count; i++) { 398 if (Items[i].Selected == true) { 399 if (n == temp.Length) { 400 int[] t = new int[n+n]; 401 temp.CopyTo(t,0); 402 temp = t; 403 } 404 temp[n++] = i; 405 } 406 } 407 int[] selectedIndices = new int[n]; 408 Array.Copy(temp,0,selectedIndices,0,n); 409 return selectedIndices; 410 } 411 } 412 413 /* 414 * The size of the list. 415 * A size of 1 displays a dropdown list. 416 */ 417 418 /// <devdoc> 419 /// <para> 420 /// Gets or sets the number of option items visible in the browser at a time. A 421 /// value greater that one will typically cause browsers to display a scrolling 422 /// list. 423 /// </para> 424 /// </devdoc> 425 [ 426 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) 427 ] 428 public int Size { 429 get { 430 string s = Attributes["size"]; 431 return((s != null) ? Int32.Parse(s, CultureInfo.InvariantCulture) : -1); 432 } 433 434 set { 435 Attributes["size"] = MapIntegerAttributeToString(value); 436 } 437 } 438 439 /* 440 * Value property. 441 */ 442 443 /// <devdoc> 444 /// <para> 445 /// Gets or sets the current item selected in the <see langword='HtmlSelect'/> 446 /// control. 447 /// </para> 448 /// </devdoc> 449 [ 450 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) 451 ] 452 public string Value { 453 get { 454 int i = SelectedIndex; 455 return(i < 0 || i >= Items.Count) ? String.Empty : Items[i].Value; 456 } 457 458 set { 459 int i = Items.FindByValueInternal(value, true); 460 if (i >= 0) 461 SelectedIndex = i; 462 } 463 } 464 465 466 /// <devdoc> 467 /// <para> 468 /// Occurs when an <see langword='HtmlSelect'/> control is changed on the 469 /// server. 470 /// </para> 471 /// </devdoc> 472 [ 473 WebCategory("Action"), 474 WebSysDescription(SR.HtmlSelect_OnServerChange) 475 ] 476 public event EventHandler ServerChange { 477 add { 478 Events.AddHandler(EventServerChange, value); 479 } 480 remove { 481 Events.RemoveHandler(EventServerChange, value); 482 } 483 } 484 485 486 /// <internalonly/> AddParsedSubObject(object obj)487 protected override void AddParsedSubObject(object obj) { 488 if (obj is ListItem) 489 Items.Add((ListItem)obj); 490 else 491 throw new HttpException(SR.GetString(SR.Cannot_Have_Children_Of_Type, "HtmlSelect", obj.GetType().Name)); 492 } 493 494 495 /// <internalonly/> 496 /// <devdoc> 497 /// </devdoc> ClearSelection()498 protected virtual void ClearSelection() { 499 for (int i=0; i < Items.Count; i++) 500 Items[i].Selected = false; 501 } 502 503 /// <devdoc> 504 /// Connects this data bound control to the appropriate DataSourceView 505 /// and hooks up the appropriate event listener for the 506 /// DataSourceViewChanged event. The return value is the new view (if 507 /// any) that was connected to. An exception is thrown if there is 508 /// a problem finding the requested view or data source. 509 /// </devdoc> ConnectToDataSourceView()510 private DataSourceView ConnectToDataSourceView() { 511 if (_currentViewValid && !DesignMode) { 512 // If the current view is correct, there is no need to reconnect 513 return _currentView; 514 } 515 516 // Disconnect from old view, if necessary 517 if ((_currentView != null) && (_currentViewIsFromDataSourceID)) { 518 // We only care about this event if we are bound through the DataSourceID property 519 _currentView.DataSourceViewChanged -= new EventHandler(OnDataSourceViewChanged); 520 } 521 522 // Connect to new view 523 IDataSource ds = null; 524 string dataSourceID = DataSourceID; 525 526 if (dataSourceID.Length != 0) { 527 // Try to find a DataSource control with the ID specified in DataSourceID 528 Control control = DataBoundControlHelper.FindControl(this, dataSourceID); 529 if (control == null) { 530 throw new HttpException(SR.GetString(SR.DataControl_DataSourceDoesntExist, ID, dataSourceID)); 531 } 532 ds = control as IDataSource; 533 if (ds == null) { 534 throw new HttpException(SR.GetString(SR.DataControl_DataSourceIDMustBeDataControl, ID, dataSourceID)); 535 } 536 } 537 538 if (ds == null) { 539 // DataSource control was not found, construct a temporary data source to wrap the data 540 ds = new ReadOnlyDataSource(DataSource, DataMember); 541 } 542 else { 543 // Ensure that both DataSourceID as well as DataSource are not set at the same time 544 if (DataSource != null) { 545 throw new InvalidOperationException(SR.GetString(SR.DataControl_MultipleDataSources, ID)); 546 } 547 } 548 549 // IDataSource was found, extract the appropriate view and return it 550 DataSourceView newView = ds.GetView(DataMember); 551 if (newView == null) { 552 throw new InvalidOperationException(SR.GetString(SR.DataControl_ViewNotFound, ID)); 553 } 554 555 _currentViewIsFromDataSourceID = IsBoundUsingDataSourceID; 556 _currentView = newView; 557 if ((_currentView != null) && (_currentViewIsFromDataSourceID)) { 558 // We only care about this event if we are bound through the DataSourceID property 559 _currentView.DataSourceViewChanged += new EventHandler(OnDataSourceViewChanged); 560 } 561 _currentViewValid = true; 562 563 return _currentView; 564 } 565 566 CreateControlCollection()567 protected override ControlCollection CreateControlCollection() { 568 return new EmptyControlCollection(this); 569 } 570 571 EnsureDataBound()572 protected void EnsureDataBound() { 573 try { 574 _throwOnDataPropertyChange = true; 575 if (RequiresDataBinding && DataSourceID.Length > 0) { 576 DataBind(); 577 } 578 } 579 finally{ 580 _throwOnDataPropertyChange = false; 581 } 582 } 583 584 585 /// <devdoc> 586 /// Returns an IEnumerable that is the DataSource, which either came 587 /// from the DataSource property or from the control bound via the 588 /// DataSourceID property. 589 /// </devdoc> GetData()590 protected virtual IEnumerable GetData() { 591 DataSourceView view = ConnectToDataSourceView(); 592 593 Debug.Assert(_currentViewValid); 594 595 if (view != null) { 596 return view.ExecuteSelect(DataSourceSelectArguments.Empty); 597 } 598 return null; 599 } 600 601 /* 602 * Override to load items and selected indices. 603 */ 604 605 /// <internalonly/> 606 /// <devdoc> 607 /// </devdoc> LoadViewState(object savedState)608 protected override void LoadViewState(object savedState) { 609 if (savedState != null) { 610 Triplet statetriplet = (Triplet)savedState; 611 base.LoadViewState(statetriplet.First); 612 613 // restore state of items 614 ((IStateManager)Items).LoadViewState(statetriplet.Second); 615 616 // restore selected indices 617 object selectedIndices = statetriplet.Third; 618 if (selectedIndices != null) 619 Select((int[])selectedIndices); 620 } 621 } 622 623 624 /// <internalonly/> 625 /// <devdoc> 626 /// </devdoc> OnDataBinding(EventArgs e)627 protected override void OnDataBinding(EventArgs e) { 628 base.OnDataBinding(e); 629 630 // create items using the datasource 631 IEnumerable dataSource = GetData(); 632 633 // create items using the datasource 634 if (dataSource != null) { 635 bool fieldsSpecified = false; 636 string textField = DataTextField; 637 string valueField = DataValueField; 638 639 Items.Clear(); 640 ICollection collection = dataSource as ICollection; 641 if (collection != null) { 642 Items.Capacity = collection.Count; 643 } 644 645 if ((textField.Length != 0) || (valueField.Length != 0)) 646 fieldsSpecified = true; 647 648 foreach (object dataItem in dataSource) { 649 ListItem item = new ListItem(); 650 651 if (fieldsSpecified) { 652 if (textField.Length > 0) { 653 item.Text = DataBinder.GetPropertyValue(dataItem,textField,null); 654 } 655 if (valueField.Length > 0) { 656 item.Value = DataBinder.GetPropertyValue(dataItem,valueField,null); 657 } 658 } 659 else { 660 item.Text = item.Value = dataItem.ToString(); 661 } 662 663 Items.Add(item); 664 } 665 } 666 // try to apply the cached SelectedIndex now 667 if (cachedSelectedIndex != -1) { 668 SelectedIndex = cachedSelectedIndex; 669 cachedSelectedIndex = -1; 670 } 671 ViewState[DataBoundViewStateKey] = true; 672 RequiresDataBinding = false; 673 } 674 675 676 /// <devdoc> 677 /// This method is called when DataMember, DataSource, or DataSourceID is changed. 678 /// </devdoc> OnDataPropertyChanged()679 protected virtual void OnDataPropertyChanged() { 680 if (_throwOnDataPropertyChange) { 681 throw new HttpException(SR.GetString(SR.DataBoundControl_InvalidDataPropertyChange, ID)); 682 } 683 684 if (_inited) { 685 RequiresDataBinding = true; 686 } 687 _currentViewValid = false; 688 } 689 690 OnDataSourceViewChanged(object sender, EventArgs e)691 protected virtual void OnDataSourceViewChanged(object sender, EventArgs e) { 692 RequiresDataBinding = true; 693 } 694 695 OnInit(EventArgs e)696 protected internal override void OnInit(EventArgs e) { 697 base.OnInit(e); 698 699 if (Page != null) { 700 Page.PreLoad += new EventHandler(this.OnPagePreLoad); 701 if (!IsViewStateEnabled && Page.IsPostBack) { 702 RequiresDataBinding = true; 703 } 704 } 705 } 706 707 OnLoad(EventArgs e)708 protected internal override void OnLoad(EventArgs e) { 709 _inited = true; // just in case we were added to the page after PreLoad 710 ConnectToDataSourceView(); 711 if (Page != null && !_pagePreLoadFired && ViewState[DataBoundViewStateKey] == null) { 712 // If the control was added after PagePreLoad, we still need to databind it because it missed its 713 // first change in PagePreLoad. If this control was created by a call to a parent control's DataBind 714 // in Page_Load (with is relatively common), this control will already have been databound even 715 // though pagePreLoad never fired and the page isn't a postback. 716 if (!Page.IsPostBack) { 717 RequiresDataBinding = true; 718 } 719 720 // If the control was added to the page after page.PreLoad, we'll never get the event and we'll 721 // never databind the control. So if we're catching up and Load happens but PreLoad never happened, 722 // call DataBind. This may make the control get databound twice if the user called DataBind on the control 723 // directly in Page.OnLoad, but better to bind twice than never to bind at all. 724 else if (IsViewStateEnabled) { 725 RequiresDataBinding = true; 726 } 727 } 728 729 base.OnLoad(e); 730 } 731 OnPagePreLoad(object sender, EventArgs e)732 private void OnPagePreLoad(object sender, EventArgs e) { 733 _inited = true; 734 735 if (Page != null) { 736 Page.PreLoad -= new EventHandler(this.OnPagePreLoad); 737 738 // Setting RequiresDataBinding to true in OnLoad is too late because the OnLoad page event 739 // happens before the control.OnLoad method gets called. So a page_load handler on the page 740 // that calls DataBind won't prevent DataBind from getting called again in PreRender. 741 if (!Page.IsPostBack) { 742 RequiresDataBinding = true; 743 } 744 // If this is a postback and viewstate is enabled, but we have never bound the control 745 // before, it is probably because its visibility was changed in the postback. In this 746 // case, we need to bind the control or it will never appear. This is a common scenario 747 // for Wizard and MultiView. 748 if (Page.IsPostBack && IsViewStateEnabled && ViewState[DataBoundViewStateKey] == null) { 749 RequiresDataBinding = true; 750 } 751 } 752 _pagePreLoadFired = true; 753 } 754 755 /* 756 * This method is invoked just prior to rendering. 757 */ 758 759 /// <internalonly/> 760 /// <devdoc> 761 /// </devdoc> OnPreRender(EventArgs e)762 protected internal override void OnPreRender(EventArgs e) { 763 base.OnPreRender(e); 764 765 // An Html SELECT does not post when nothing is selected. 766 if (Page != null && !Disabled) { 767 if (Size > 1) { 768 Page.RegisterRequiresPostBack(this); 769 } 770 771 Page.RegisterEnabledControl(this); 772 } 773 774 EnsureDataBound(); 775 } 776 777 /* 778 * Method used to raise the OnServerChange event. 779 */ 780 781 /// <devdoc> 782 /// <para> 783 /// Raised 784 /// on the server when the <see langword='HtmlSelect'/> control list values 785 /// change between postback requests. 786 /// </para> 787 /// </devdoc> OnServerChange(EventArgs e)788 protected virtual void OnServerChange(EventArgs e) { 789 EventHandler handler = (EventHandler)Events[EventServerChange]; 790 if (handler != null) handler(this, e); 791 } 792 793 /* 794 * Override to prevent SelectedIndex from being rendered as an attribute. 795 */ 796 797 /// <internalonly/> 798 /// <devdoc> 799 /// </devdoc> RenderAttributes(HtmlTextWriter writer)800 protected override void RenderAttributes(HtmlTextWriter writer) { 801 if (Page != null) { 802 Page.ClientScript.RegisterForEventValidation(RenderedNameAttribute); 803 } 804 805 writer.WriteAttribute("name", RenderedNameAttribute); 806 Attributes.Remove("name"); 807 808 Attributes.Remove("DataValueField"); 809 Attributes.Remove("DataTextField"); 810 Attributes.Remove("DataMember"); 811 Attributes.Remove("DataSourceID"); 812 base.RenderAttributes(writer); 813 } 814 815 /* 816 * Render the Items in the list. 817 */ 818 819 /// <internalonly/> 820 /// <devdoc> 821 /// </devdoc> RenderChildren(HtmlTextWriter writer)822 protected internal override void RenderChildren(HtmlTextWriter writer) { 823 bool selected = false; 824 bool isSingle = !Multiple; 825 826 writer.WriteLine(); 827 writer.Indent++; 828 ListItemCollection liCollection = Items; 829 int n = liCollection.Count; 830 if (n > 0) { 831 for (int i=0; i < n; i++) { 832 ListItem li = liCollection[i]; 833 writer.WriteBeginTag("option"); 834 if (li.Selected) { 835 if (isSingle) 836 { 837 if (selected) 838 throw new HttpException(SR.GetString(SR.HtmlSelect_Cant_Multiselect_In_Single_Mode)); 839 selected=true; 840 } 841 writer.WriteAttribute("selected", "selected"); 842 } 843 844 writer.WriteAttribute("value", li.Value, true /*fEncode*/); 845 846 // This is to fix the case where the user puts one of these 847 // three values in the AttributeCollection. Removing them 848 // at least is better than rendering them twice. 849 li.Attributes.Remove("text"); 850 li.Attributes.Remove("value"); 851 li.Attributes.Remove("selected"); 852 853 li.Attributes.Render(writer); 854 writer.Write(HtmlTextWriter.TagRightChar); 855 HttpUtility.HtmlEncode(li.Text, writer); 856 writer.WriteEndTag("option"); 857 writer.WriteLine(); 858 } 859 } 860 writer.Indent--; 861 } 862 863 /* 864 * Save selected indices and modified Items. 865 */ 866 867 /// <internalonly/> 868 /// <devdoc> 869 /// </devdoc> SaveViewState()870 protected override object SaveViewState() { 871 872 object baseState = base.SaveViewState(); 873 object items = ((IStateManager)Items).SaveViewState(); 874 object selectedindices = null; 875 876 // only save selection if handler is registered, 877 // we are disabled, or we are not visible 878 // since selection is always posted back otherwise 879 if (Events[EventServerChange] != null || Disabled || !Visible) 880 selectedindices = SelectedIndices; 881 882 if (selectedindices != null || items != null || baseState != null) 883 return new Triplet(baseState, items, selectedindices); 884 885 return null; 886 } 887 888 889 /// <internalonly/> 890 /// <devdoc> 891 /// </devdoc> Select(int[] selectedIndices)892 protected virtual void Select(int[] selectedIndices) { 893 ClearSelection(); 894 for (int i=0; i < selectedIndices.Length; i++) { 895 int n = selectedIndices[i]; 896 if (n >= 0 && n < Items.Count) 897 Items[n].Selected = true; 898 } 899 } 900 901 /* 902 * TrackState 903 */ 904 905 /// <internalonly/> 906 /// <devdoc> 907 /// </devdoc> TrackViewState()908 protected override void TrackViewState() { 909 base.TrackViewState(); 910 ((IStateManager)Items).TrackViewState(); 911 } 912 913 914 /* 915 * Method of IPostBackDataHandler interface to process posted data. 916 * SelectList processes a newly posted value. 917 */ 918 919 /// <internalonly/> 920 /// <devdoc> 921 /// </devdoc> IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection)922 bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection) { 923 return LoadPostData(postDataKey, postCollection); 924 } 925 926 927 /// <internalonly/> 928 /// <devdoc> 929 /// </devdoc> LoadPostData(string postDataKey, NameValueCollection postCollection)930 protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) { 931 string[] selectedItems = postCollection.GetValues(postDataKey); 932 bool selectionChanged = false; 933 934 if (selectedItems != null) { 935 if (!Multiple) { 936 int n = Items.FindByValueInternal(selectedItems[0], false); 937 if (SelectedIndex != n) { 938 SelectedIndex = n; 939 selectionChanged = true; 940 } 941 } 942 else { // multiple selection 943 int count = selectedItems.Length; 944 int[] oldSelectedIndices = SelectedIndices; 945 int[] newSelectedIndices = new int[count]; 946 for (int i=0; i < count; i++) { 947 // create array of new indices from posted values 948 newSelectedIndices[i] = Items.FindByValueInternal(selectedItems[i], false); 949 } 950 951 if (oldSelectedIndices.Length == count) { 952 // check new indices against old indices 953 // assumes selected values are posted in order 954 for (int i=0; i < count; i++) { 955 if (newSelectedIndices[i] != oldSelectedIndices[i]) { 956 selectionChanged = true; 957 break; 958 } 959 } 960 } 961 else { 962 // indices must have changed if count is different 963 selectionChanged = true; 964 } 965 966 if (selectionChanged) { 967 // select new indices 968 Select(newSelectedIndices); 969 } 970 } 971 } 972 else { // no items selected 973 if (SelectedIndex != -1) { 974 SelectedIndex = -1; 975 selectionChanged = true; 976 } 977 } 978 979 if (selectionChanged) { 980 ValidateEvent(postDataKey); 981 } 982 983 return selectionChanged; 984 } 985 986 /* 987 * Method of IPostBackDataHandler interface which is invoked whenever posted data 988 * for a control has changed. SelectList fires an OnServerChange event. 989 */ 990 991 /// <internalonly/> 992 /// <devdoc> 993 /// </devdoc> IPostBackDataHandler.RaisePostDataChangedEvent()994 void IPostBackDataHandler.RaisePostDataChangedEvent() { 995 RaisePostDataChangedEvent(); 996 } 997 998 999 /// <internalonly/> 1000 /// <devdoc> 1001 /// </devdoc> RaisePostDataChangedEvent()1002 protected virtual void RaisePostDataChangedEvent() { 1003 OnServerChange(EventArgs.Empty); 1004 } 1005 } 1006 } 1007