介绍 最近,我需要创建一个应用程序,其中某些特性是打开还是关闭取决于客户。其中一组特性包含在一对列表框中,但我找不到任何方法禁用列表中的某些项。我决定创建自己的类,它派生于ListBox,这是我最后的结果。 现在与数据绑定!它实际上没有我想象的那么复杂。下面的更多信息。 使用的代码 DisableListBox包含一个布尔值列表listenable,它定义了启用和禁用哪些项(项中的)。listenable [i]为真,表示项目[i]被启用。我提供了用于添加/插入/删除/移除和清除项的函数,因此您不必担心保持两个列表同步。 隐藏,复制Code
public void AddItem(object item, bool enabled); public void InsertItem(int index, object item, bool enabled); public void RemoveItem(object item); public void RemoveItemAt(int index); public void ClearItems();
现在,如果你想启用或禁用项目后,添加他们,我创建了几个简单的方法: 隐藏,复制Code
public void EnableItem(object item); public void EnableItemAt(int index); public void DisableItem(object item); public void DisableItemAt(int index);
我还公开了itemenable,以防您自己想搞乱Items和itemenable列表。 现在是有趣的部分。版本1.1允许数据绑定。控件公开一个属性EnableMember,它的工作方式与DisplayMember和ValueMember类似。EnableMember将获取属性中的值,并使用它来确定是否启用了该项。它使用转换。ToBoolean,这样它可以处理数值类型和字符串。如果在数据绑定时没有设置EnableMember,那么所有项都将默认为enabled。这是代码运行时,EnableMember或数据源改变: 隐藏,收缩,复制Code
private void RefreshItemEnables() { // Get enable property GetEnableProperty(); // Clear enable list ItemEnables.Clear(); // Fill enable list for (int i = 0; i < Items.Count; i++) ItemEnables.Add(ProduceEnable(i)); // Ensure disabled items are not selected for (int i = SelectedItems.Count - 1; i >= 0; i--) if (!ItemEnables[SelectedIndices[i]]) SelectedItems.Remove(SelectedItems[i]); } private void GetEnableProperty() { // If it should be bound to a property if (DataSource != null && EnableMember != string.Empty) { // Clear property enableProperty = null; // Find property foreach (PropertyDescriptor property in DataManager.GetItemProperties()) if (property.Name == enableMember) enableProperty = property; } } private bool ProduceEnable(int i) { // If databound and enable property is set if (DataSource != null && enableProperty != null) try { // Convert property to boolean return Convert.ToBoolean(enableProperty.GetValue(Items[i])); } // Object couldn't be converted to boolean catch (InvalidCastException) { return false; } else return true; }
我还必须处理数据源中的项发生变化时,这是通过注册一些ListBox的DataManager(实际上是一个CurrencyManager)事件来完成的。 隐藏,复制Code
void DataManager_ListChanged(object sender, ListChangedEventArgs e) { switch (e.ListChangedType) { // Handle items being added case ListChangedType.ItemAdded: ItemEnables.Insert(e.NewIndex, ProduceEnable(e.NewIndex)); break; // Handle items being deleted case ListChangedType.ItemDeleted: ItemEnables.RemoveAt(e.NewIndex); break; } } void DataManager_ItemChanged(object sender, ItemChangedEventArgs e) { // Handle items changing if (e.Index > -1) SetEnabledAt(e.Index, ProduceEnable(e.Index)); }
很多工作都是为了确保残障物品不能被选中。为此,我不得不重写WndProc和捕捉以下消息: 隐藏,复制Code
// Page Up/Down private const int VK_PRIOR = 0x21; private const int VK_NEXT = 0x22; // End/Home private const int VK_END = 0x23; private const int VK_HOME = 0x24; // Arrow keys private const int VK_LEFT = 0x25; private const int VK_UP = 0x26; private const int VK_RIGHT = 0x27; private const int VK_DOWN = 0x28; private const int WM_KEYDOWN = 0x100; private const int WM_MOUSEMOVE = 0x200; private const int WM_LBUTTONDOWN = 0x201;
首先我处理了鼠标选择: 隐藏,复制Code
// Intercept mouse selection if (m.Msg == WM_MOUSEMOVE || m.Msg == WM_LBUTTONDOWN) { // Get mouse location Point clickedPt = new Point(); clickedPt.X = lParam & 0x0000FFFF; clickedPt.Y = lParam >> 16; // If point is on a disabled item, ignore mouse for (int i = 0; i < Items.Count; i++) if (!ItemEnables[i] && GetItemRectangle(i).Contains(clickedPt)) return; }
然后键盘选择(为了简洁,我只显示了一半的代码): 隐藏,收缩,复制Code
// Intercept keyboard selection if (m.Msg == WM_KEYDOWN) // Handle single down if (wParam == VK_DOWN || wParam == VK_RIGHT) { // Select next enabled item for (int i = SelectedIndex + 1; i < Items.Count; i++) if (ItemEnables[i]) { SelectedIndex = i; break; } return; } // Handle single up else if (wParam == VK_UP || wParam == VK_LEFT) { ... } // Handle page up else if (wParam == VK_PRIOR) { // Ignore if empty if (ItemEnables.Count == 0) return; // Get current selected index int currentIndex = Math.Max(0, SelectedIndex); // Get number of items to jump int toJump = NumVisibleItems() - 1; // Check if there are enough items to jump a full page if (currentIndex >= toJump) { // Jump at least a full page if possible for (int i = currentIndex - toJump; i >= 0; i--) if (ItemEnables[i]) { SelectedIndex = i; return; } } // If there aren't enough items, try to jump as far as possible else toJump = currentIndex; // Jump as far as possible without ending on a disabled item for (int i = currentIndex - toJump; i <= currentIndex; i++) if (ItemEnables[i]) { SelectedIndex = i; break; } return; } // Handle page down else if (wParam == VK_NEXT) { ... } // Handle end else if (wParam == VK_END) { // Select closest enabled item to end for (int i = ItemEnables.Count - 1; i >= 0; i--) if (ItemEnables[i]) { SelectedIndex = i; break; } return; } // Handle home else if (wParam == VK_HOME) { ... }
最后的任务是处理被禁用物品的绘制。这是通过在第一次设置DrawMode = DrawMode. ownerdrawfixed后重写OnDrawItem来完成的;在构造函数中。我决定公开两个属性EnabledItemColor和DisabledItemColor来设置项目的文本颜色。它们在构造函数中分别默认为黑色和灰色。我还添加了一些代码来处理设置的right ttoleft,并确保它完全像一个列表框一样显示。 隐藏,收缩,复制Code
protected override void OnDrawItem(DrawItemEventArgs e) { // Stops control from throwing errors if empty or in design mode if (e.Index > -1 && !suspendDraw && !IsDesignMode()) { // Draw the background e.DrawBackground(); // Select color to use Color color; if (Enabled && ItemEnables[e.Index]) if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) color = Color.White; else color = EnabledItemColor; else color = DisabledItemColor; // Align text Rectangle shiftedBounds; TextFormatFlags alignment; if (base.RightToLeft == RightToLeft.No) { // To look the same as ListBox, the bounds have to be shifted shiftedBounds = new Rectangle(e.Bounds.X - 1, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height); alignment = TextFormatFlags.Left; } else { // To look the same as ListBox, the bounds have to be shifted shiftedBounds = new Rectangle(e.Bounds.X + 2, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height); alignment = TextFormatFlags.Right; } // Get string to display string displayString = GetItemText(Items[e.Index]); // Draw the string TextRenderer.DrawText(e.Graphics, displayString, e.Font, shiftedBounds, color, alignment); // Draw the focus rectangle e.DrawFocusRectangle(); } // Call base OnDrawItem base.OnDrawItem(e); }
的兴趣点 我很高兴地尝试让DisableListBox的名称显示在设计模式中,就像ListBox那样。基本上,这需要在设计器中将DrawMode设置为Normal,而在其他地方设置为OwnerDrawFixed。下面是所有处理这个的代码: 隐藏,收缩,复制Code
// Set to normal so name shows up in design mode private DrawMode drawMode = DrawMode.Normal; /// <summary> /// Gets or sets the drawing mode for the control. /// </summary> [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] public override DrawMode DrawMode { get { return drawMode; } set { drawMode = value; // Keeps base.DrawMode set to Normal so name shows up in the designer if (!IsDesignMode()) base.DrawMode = value; } } /// <summary> /// Initializes a new instance of the DisableListBox class. /// </summary> public DisableListBox() { DrawMode = DrawMode.OwnerDrawFixed; } private bool IsDesignMode() { return DesignMode || LicenseManager.UsageMode == LicenseUsageMode.Designtime; }
IsDesignMode()方法之所以存在,是因为设计模式不喜欢一直工作。你可以在这里了解更多。 特别感谢MSDN论坛上的Hans Passant (nobugz)和Nishant Sivakumar帮助我解决了这个问题。 已知的缺陷(s) 在绑定数据时更改EnableMember将导致禁用已启用的项。如果先前启用的项是选中的项,则将取消选中。不幸的是,CurrencyManager(处理数据绑定)没有“未选中”的位置。如果数据源在您选择其他内容之前发生了更改,则列表将刷新,禁用的项现在将被选中。 我不知道如何解决这个问题,因为总是有这样的情况,所有的项目都是禁用的,所以我不能只是设置它为另一个项目,而不是删除选择。此外,在这种情况下,onselectedindexchange事件甚至不会在项目被重新选择时触发。我不知道如何解决这个问题,所以欢迎任何建议。 另一个“bug”是绘制模式、选择模式和排序属性被隐藏了。DrawMode一直隐藏,因为改变正常不显示禁用物品,如果你要改变它OwnerDrawVariable你将不得不改变OnDrawItem代码无论如何,所以在这一点上你可以取消隐藏它如果你希望能够OwnerDrawFixed和OwnerDrawVariable之间切换。 排序没有实现,因为要保持两个列表(Items和itemenable)同步,我必须在类中编写自定义排序,这似乎有点浪费。另外,我怀疑没有人会经常使用它。如果有人想看到它添加,只要评论,我们会看到。 选择模式是隐藏的,因为多选择选项真的把事情搞砸了。我不知道如何处理在中间有禁用项目时的shift-click,所以像排序一样,我不会实现这个除非我得到它的请求。 历史 2009年7月29日 更新为v1.2Added EnableMemberError火灾事件如果EnableMember值不能转换为布尔值,给予例外和itemBug修复的索引:添加空构造函数抛出一个exceptionBug修复后数据源:更新绑定数据源抛出一个exceptionBug疗法:页面上/下和家庭/结束会让您选择禁用itemsBug预防:不允许改变SelectionMode和排序,直到实现这些功能 2009年7月17日, 更新到1.1,增加了数据绑定和更好的绘图 2009年7月10日, 提交文章 本文转载于:http://www.diyabc.com/frontweb/news372.html