DropDownList.Items、GridView.Columns、ValidatorCollection 等集合屬性,讓開發者能以宣告式標籤配置多項目。本篇完整實作集合屬性架構:集合類別、項目類別、集合編輯器、設計時持久化、ControlBuilder 整合。
ListItem, DataControlField)CollectionBase 或 List<T>,管理項目群組
[TypeConverter(typeof(DropItemConverter))]
public class DropItem
{
private string _text = string.Empty;
private string _value = string.Empty;
private Color _backColor = Color.Empty;
private Color _foreColor = Color.Empty;
private bool _enabled = true;
private string _toolTip = string.Empty;
private object _tag;
[Category("Appearance")]
[DefaultValue("")]
[Description("項目顯示文字")]
[NotifyParentProperty(true)] // 通知父集合屬性已變更
public string Text
{
get => _text;
set { _text = value ?? string.Empty; OnChanged(); }
}
[Category("Data")]
[DefaultValue("")]
[Description("項目實際值")]
[NotifyParentProperty(true)]
public string Value
{
get => _value;
set { _value = value ?? string.Empty; OnChanged(); }
}
[Category("Appearance")]
[DefaultValue(typeof(Color), "")]
[Description("背景顏色")]
[NotifyParentProperty(true)]
[TypeConverter(typeof(WebColorConverter))]
public Color BackColor
{
get => _backColor;
set { _backColor = value; OnChanged(); }
}
[Category("Appearance")]
[DefaultValue(typeof(Color), "")]
[Description("前景顏色")]
[NotifyParentProperty(true)]
[TypeConverter(typeof(WebColorConverter))]
public Color ForeColor
{
get => _foreColor;
set { _foreColor = value; OnChanged(); }
}
[Category("Behavior")]
[DefaultValue(true)]
[Description("是否啟用")]
[NotifyParentProperty(true)]
public bool Enabled
{
get => _enabled;
set { _enabled = value; OnChanged(); }
}
[Category("Appearance")]
[DefaultValue("")]
[Description("工具提示")]
[NotifyParentProperty(true)]
public string ToolTip
{
get => _toolTip;
set { _toolTip = value ?? string.Empty; OnChanged(); }
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public object Tag
{
get => _tag;
set => _tag = value;
}
// 通知父集合變更
protected virtual void OnChanged()
{
// 實作可透過事件或介面通知父集合
}
public override string ToString() => Text;
}
public class DropItemConverter : TypeConverter
{
public override bool GetPropertiesSupported(ITypeDescriptorContext context) => true;
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(DropItem));
return props.Sort(new[] { "Text", "Value", "Enabled", "BackColor", "ForeColor", "ToolTip" });
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(InstanceDescriptor) || base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(InstanceDescriptor) && value is DropItem item)
{
ConstructorInfo ctor = typeof(DropItem).GetConstructor(Type.EmptyTypes);
return new InstanceDescriptor(ctor, null, false);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
[CollectionEditor(typeof(DropItemCollectionEditor))]
public class DropItemCollection : CollectionBase
{
public DropItem this[int index]
{
get => (DropItem)List[index];
set => List[index] = value;
}
public int Add(DropItem item)
{
item.Changed += Item_Changed; // 訂閱項目變更事件
return List.Add(item);
}
public void Remove(DropItem item)
{
item.Changed -= Item_Changed;
List.Remove(item);
}
public void Insert(int index, DropItem item)
{
item.Changed += Item_Changed;
List.Insert(index, item);
}
public bool Contains(DropItem item) => List.Contains(item);
public int IndexOf(DropItem item) => List.IndexOf(item);
public void CopyTo(DropItem[] array, int index) => List.CopyTo(array, index);
// 事件:項目屬性變更通知
public event EventHandler CollectionChanged;
private void Item_Changed(object sender, EventArgs e)
{
CollectionChanged?.Invoke(this, EventArgs.Empty);
}
// 視圖狀態支援 (若集合本身需持久化)
private StateBag _viewState;
public StateBag ViewState => _viewState ??= new StateBag();
}
public class DropItemCollectionEditor : CollectionEditor
{
public DropItemCollectionEditor(Type type) : base(type) { }
// 自訂新增項目型別
protected override Type[] CreateNewItemTypes()
{
return new[] { typeof(DropItem) };
}
// 自訂表單標題
protected override string GetDisplayText(object value)
{
return value is DropItem item ? item.Text : base.GetDisplayText(value);
}
// 自訂編輯表單
protected override CollectionForm CreateCollectionForm()
{
CollectionForm form = base.CreateCollectionForm();
form.Text = "下拉選單項目集合編輯器";
// 可進一步自訂表單 UI
return form;
}
}
[ControlBuilder(typeof(DropDownListControlBuilder))]
public class CustomDropDownList : WebControl, INamingContainer, IPostBackDataHandler
{
private DropItemCollection _items;
[Category("Data")]
[DefaultValue(null)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[PersistenceMode(PersistenceMode.InnerProperty)]
[Editor(typeof(DropItemCollectionEditor), typeof(UITypeEditor))]
[MergableProperty(false)] // 不合併繼承層級的集合
public DropItemCollection Items
{
get
{
if (_items == null)
{
_items = new DropItemCollection();
if (IsTrackingViewState)
((IStateManager)_items).TrackViewState();
}
return _items;
}
}
// ControlBuilder 限制只允許 ListItem 標籤
public class DropDownListControlBuilder : ControlBuilder
{
public override Type GetChildControlType(string tagName, IDictionary attributes)
{
if (tagName == "asp:ListItem" || tagName == "ListItem")
return typeof(DropItem);
return null;
}
}
}
protected override void RenderContents(HtmlTextWriter writer)
{
writer.RenderBeginTag(HtmlTextWriterTag.Select);
writer.AddAttribute(HtmlTextWriterAttribute.Id, ClientID);
writer.AddAttribute(HtmlTextWriterAttribute.Name, UniqueID);
foreach (DropItem item in Items)
{
writer.RenderBeginTag(HtmlTextWriterTag.Option);
writer.AddAttribute(HtmlTextWriterAttribute.Value, item.Value);
if (!item.Enabled)
writer.AddAttribute(HtmlTextWriterAttribute.Disabled, "disabled");
if (!string.IsNullOrEmpty(item.ToolTip))
writer.AddAttribute(HtmlTextWriterAttribute.Title, item.ToolTip);
if (!item.BackColor.IsEmpty)
writer.AddStyleAttribute(HtmlTextWriterStyle.BackgroundColor, ColorTranslator.ToHtml(item.BackColor));
if (!item.ForeColor.IsEmpty)
writer.AddStyleAttribute(HtmlTextWriterStyle.Color, ColorTranslator.ToHtml(item.ForeColor));
writer.WriteEncodedText(item.Text);
writer.RenderEndTag(); // option
}
writer.RenderEndTag(); // select
}
protected override object SaveViewState()
{
object baseState = base.SaveViewState();
object itemsState = _items != null ? _items.ViewState.SaveViewState() : null;
return new Pair(baseState, itemsState);
}
protected override void LoadViewState(object savedState)
{
if (savedState == null) return;
Pair state = (Pair)savedState;
base.LoadViewState(state.First);
if (state.Second != null && _items != null)
_items.ViewState.LoadViewState(state.Second);
}
public class DataBoundDropDownList : CustomDropDownList
{
public string DataTextField { get; set; }
public string DataValueField { get; set; }
public string DataColorField { get; set; }
protected override void PerformDataBinding(IEnumerable dataSource)
{
base.PerformDataBinding(dataSource);
Items.Clear();
PropertyDescriptor textProp = DataTextField != null ? TypeDescriptor.GetProperties(dataSource).Find(DataTextField, true) : null;
PropertyDescriptor valueProp = DataValueField != null ? TypeDescriptor.GetProperties(dataSource).Find(DataValueField, true) : null;
PropertyDescriptor colorProp = DataColorField != null ? TypeDescriptor.GetProperties(dataSource).Find(DataColorField, true) : null;
foreach (object item in dataSource)
{
DropItem dropItem = new DropItem
{
Text = textProp?.GetValue(item)?.ToString() ?? item.ToString(),
Value = valueProp?.GetValue(item)?.ToString() ?? item.ToString(),
};
if (colorProp != null)
{
object colorValue = colorProp.GetValue(item);
if (colorValue is Color c) dropItem.BackColor = c;
else if (colorValue is string s) dropItem.BackColor = ColorTranslator.FromHtml(s);
}
Items.Add(dropItem);
}
}
}
集合屬性完整實作包含:
NotifyParentProperty、TypeConverter 支援屬性網格CollectionBase、項目變更事件、CollectionEditor 視覺化編輯DesignerSerializationVisibility.Content、PersistenceMode.InnerProperty、ControlBuilder 限制標籤PerformDataBinding 自動從資料源建立項目此架構支援 DropDownList、ListBox、CheckBoxList、Menu、TreeView 等所有集合型控件的核心需求。