Mark Kram
Mark Kram

Reputation: 5832

Disabling particular Items in a Combobox

I have a WinForms App and I was wondering if there was a more elegant way of disabling Combobox item without changing the SelectedIndex property -1 for all disabled values.

I have been googling and a lot of the solutions involve ASP.Net DropDownLists but this LINK looks promising. I think I may have to build my own ComboBox control but before I re-invent the wheel I figure I would ask here if it was possible.

UPDATE

Here is the final solution, thanks to Arif Eqbal:

//Add a Combobox to a form and name it comboBox1
//
    using System;
    using System.Drawing;
    using System.Windows.Forms;

    namespace WindowsFormsApplication6
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
                this.comboBox1.DrawMode = DrawMode.OwnerDrawFixed;
                this.comboBox1.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.comboBox1_DrawItem);
                this.comboBox1.SelectedIndexChanged += new System.EventHandler(this.comboBox1_SelectedIndexChanged);
            }

            private void Form1_Load(object sender, EventArgs e)
            {
                this.comboBox1.Items.Add("Test1");
                this.comboBox1.Items.Add("Test2");
                this.comboBox1.Items.Add("Test3");
                this.comboBox1.Items.Add("Test4");
                this.comboBox1.Items.Add("Test5");
                this.comboBox1.Items.Add("Test6");
                this.comboBox1.Items.Add("Test7");
            }

            Font myFont = new Font("Aerial", 10, FontStyle.Underline|FontStyle.Regular);
            Font myFont2 = new Font("Aerial", 10, FontStyle.Italic|FontStyle.Strikeout);

            private void comboBox1_DrawItem(object sender, DrawItemEventArgs e)
            {
                if (e.Index == 1 || e.Index == 4 || e.Index == 5)//We are disabling item based on Index, you can have your logic here
                {
                    e.Graphics.DrawString(comboBox1.Items[e.Index].ToString(), myFont2, Brushes.LightSlateGray, e.Bounds);
                }
                else
                {
                    e.DrawBackground();
                    e.Graphics.DrawString(comboBox1.Items[e.Index].ToString(), myFont, Brushes.Black, e.Bounds);
                    e.DrawFocusRectangle();
                }
            }

            void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
            {
                if (comboBox1.SelectedIndex == 1 || comboBox1.SelectedIndex == 4 || comboBox1.SelectedIndex == 5)
                    comboBox1.SelectedIndex = -1;
            }
        }
    }

Upvotes: 22

Views: 52012

Answers (3)

Arif Eqbal
Arif Eqbal

Reputation: 3138

Try this... Does it serve your purpose:

I assume you have a ComboBox called comboBox1 and you want to disable the second item i.e. an item with index 1.

Set the DrawMode property of the combobox to OwnerDrawFixed then handle these two events as shown below:

Font myFont = new Font("Arial", 10, FontStyle.Regular);

private void comboBox1_DrawItem(object sender, DrawItemEventArgs e)
{        
    if (e.Index == 1) //We are disabling item based on Index, you can have your logic here
    {
        e.Graphics.DrawString(comboBox1.Items[e.Index].ToString(), myFont, Brushes.LightGray, e.Bounds);
    }
    else
    {
        e.DrawBackground();
        e.Graphics.DrawString(comboBox1.Items[e.Index].ToString(), myFont, Brushes.Black, e.Bounds);
        e.DrawFocusRectangle();
    }
} 

void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
    if (comboBox1.SelectedIndex == 1)
        comboBox1.SelectedIndex = -1;
}

Upvotes: 33

user276648
user276648

Reputation: 6363

Here's my answer based 100% on Arif Eqbal's. The improvements are:

  • reuse the Font from the ComboBox instead of creating new ones (so that if you change it in the designer, you won't have to update the code)
  • reuse the default SystemBrushes (so it should match your theme ; it won't work if you manually change the colors used in the ComboBox though)
  • for the disabled items I had to redraw the background, else each time the grayed items are redrawn, their color get closer and closer to black
  • create a dedicated IsItemDisabled method to avoid copy/paste

// Don't forget to change DrawMode, else the DrawItem event won't be called.
// this.comboBox1.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;

private void comboBox1_DrawItem(object sender, DrawItemEventArgs e)
{
    ComboBox comboBox = (ComboBox)sender;

    if (IsItemDisabled(e.Index))
    {
        // NOTE we must draw the background or else each time we hover over the text it will be redrawn and its color will get darker and darker.
        e.Graphics.FillRectangle(SystemBrushes.Window, e.Bounds);
        e.Graphics.DrawString(comboBox.Items[e.Index].ToString(), comboBox.Font, SystemBrushes.GrayText, e.Bounds);
    }
    else
    {
        e.DrawBackground();

        // Using winwaed's advice for selected items:
        // Set the brush according to whether the item is selected or not
        Brush brush = ( (e.State & DrawItemState.Selected) > 0) ? SystemBrushes.HighlightText : SystemBrushes.ControlText;
        e.Graphics.DrawString(comboBox.Items[e.Index].ToString(), comboBox.Font, brush, e.Bounds);

        e.DrawFocusRectangle();
    }
}

void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
    if (IsItemDisabled(comboBox1.SelectedIndex))
        comboBox1.SelectedIndex = -1;
}

bool IsItemDisabled(int index)
{
    // We are disabling item based on Index, you can have your logic here
    return index % 2 == 1;
}

Upvotes: 20

winwaed
winwaed

Reputation: 7801

Here's a further modification. The problem with the above solutions is that a selected item isn't visible because the font foreground and the background selection are both dark. Hence the font should be set according to the value of e.State:

    private void comboBox1_DrawItem(object sender, DrawItemEventArgs e)
    {
        ComboBox comboBox = (ComboBox)sender;
        if (e.Index >= 0)
        {
            if (IsItemDisabled(e.Index))
            {
                e.Graphics.FillRectangle(SystemBrushes.Window, e.Bounds);
                e.Graphics.DrawString(comboBox.Items[e.Index].ToString(), comboBox.Font, Brushes.LightSlateGray, e.Bounds);
            }
            else
            {
                e.DrawBackground();

                // Set the brush according to whether the item is selected or not
                Brush br = ( (e.State & DrawItemState.Selected) > 0) ? SystemBrushes.HighlightText : SystemBrushes.ControlText;

                e.Graphics.DrawString(comboBox.Items[e.Index].ToString(), comboBox.Font, br, e.Bounds);
                e.DrawFocusRectangle();
            }
        }
    }

Upvotes: 5

Related Questions