列表框禁用的项目

Demo project in action. 介绍 最近,我需要创建一个应用程序,其中某些特性是打开还是关闭取决于客户。其中一组特性包含在一对列表框中,但我找不到任何方法禁用列表中的某些项。我决定创建自己的类,它派生于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

原文地址:https://www.cnblogs.com/Dincat/p/13443912.html