views:

13

answers:

1

I have a case where I would like TreeView to be able to show radio buttons on multiple root nodes, and checkboxes on their children. There would only be one level of children beneath any root node.

The radios should also behave like a group, ie one root is selected and the others' radios deselect.

I've been trying to fake it with images, but it doesn't look realistic. I originally had a listbox and a separate checkedlistbox, but the usability gods struck it down.

Has anyone implemented this functionality or have another suggestion?

Think of it this way:

(o) McDonalds
   [ ] Burger
   [ ] Fries
   [ ] Drink
(o) Burger King
   [ ] Burger
   [ ] Fries
   [ ] Drink
(*) Wendy's
   [x] Burger
   [x] Fries
   [ ] Drink

You can have one big option, but select 1..n underneath the big option.

A: 

I came up with a solution based on the article http://www.codeproject.com/KB/combobox/RadioListBoxDotNetVersion.aspx. My implementation inherits from CheckedListBox and includes the following methods:

    protected override void OnDrawItem (DrawItemEventArgs e)
    {
        // Erase all background if control has no items
        if (e.Index < 0 || e.Index > this.Items.Count - 1)
        {
            e.Graphics.FillRectangle(new SolidBrush(this.BackColor), this.ClientRectangle);
            return;
        }

        // Calculate bounds for background, if last item paint up to bottom of control
        Rectangle rectBack = e.Bounds;
        if (e.Index == this.Items.Count - 1)
            rectBack.Height = this.ClientRectangle.Top + this.ClientRectangle.Height - e.Bounds.Top;
        e.Graphics.FillRectangle(new SolidBrush(this.BackColor), rectBack);

        // Determines text color/brush
        Brush brushText = SystemBrushes.FromSystemColor(this.ForeColor);
        if ((e.State & DrawItemState.Disabled) == DrawItemState.Disabled || (e.State & DrawItemState.Grayed) == DrawItemState.Grayed)
            brushText = SystemBrushes.GrayText;
        Boolean bIsChecked = this.GetItemChecked(e.Index);

        String strText;
        if (!string.IsNullOrEmpty(DisplayMember)) // Bound Datatable? Then show the column written in Displaymember
            strText = ((System.Data.DataRowView) this.Items[e.Index])[this.DisplayMember].ToString();
        else
            strText = this.Items[e.Index].ToString();

        Size sizeGlyph;
        Point ptGlyph;

        if (this.GetItemType(e.Index) == ItemType.Radio)
        {
            RadioButtonState stateRadio = bIsChecked ? RadioButtonState.CheckedNormal : RadioButtonState.UncheckedNormal;
            if ((e.State & DrawItemState.Disabled) == DrawItemState.Disabled || (e.State & DrawItemState.Grayed) == DrawItemState.Grayed)
                stateRadio = bIsChecked ? RadioButtonState.CheckedDisabled : RadioButtonState.UncheckedDisabled;

            // Determines bounds for text and radio button
            sizeGlyph = RadioButtonRenderer.GetGlyphSize(e.Graphics, stateRadio);
            ptGlyph = e.Bounds.Location;
            ptGlyph.X += 4; // a comfortable distance from the edge
            ptGlyph.Y += (e.Bounds.Height - sizeGlyph.Height) / 2;

            // Draws the radio button
            RadioButtonRenderer.DrawRadioButton(e.Graphics, ptGlyph, stateRadio);
        }
        else
        {
            CheckBoxState stateCheck = bIsChecked ? CheckBoxState.CheckedNormal : CheckBoxState.UncheckedNormal;
            if ((e.State & DrawItemState.Disabled) == DrawItemState.Disabled || (e.State & DrawItemState.Grayed) == DrawItemState.Grayed)
                stateCheck = bIsChecked ? CheckBoxState.CheckedDisabled : CheckBoxState.UncheckedDisabled;

            // Determines bounds for text and radio button
            sizeGlyph = CheckBoxRenderer.GetGlyphSize(e.Graphics, stateCheck);
            ptGlyph = e.Bounds.Location;
            ptGlyph.X += 20; // a comfortable distance from the edge
            ptGlyph.Y += (e.Bounds.Height - sizeGlyph.Height) / 2;

            // Draws the radio button
            CheckBoxRenderer.DrawCheckBox(e.Graphics, ptGlyph, stateCheck);
        }

        // Draws the text
        Rectangle rectText = new Rectangle(ptGlyph.X + sizeGlyph.Width + 3, e.Bounds.Y, e.Bounds.Width - sizeGlyph.Width, e.Bounds.Height);
        e.Graphics.DrawString(strText.Substring(4), e.Font, brushText, rectText, this.oAlign);

        // If the ListBox has focus, draw a focus rectangle around the selected item.
        e.DrawFocusRectangle();
    }

    protected override void OnItemCheck (ItemCheckEventArgs ice)
    {
        base.OnItemCheck(ice);

        if (ice.NewValue == CheckState.Unchecked)
            return;

        if (this.GetItemType(ice.Index) == ItemType.Radio) // if they selected a root, deselect other roots and their children
        {
            for (Int32 i = 0; i < this.Items.Count; ++i)
            {
                if (i == ice.Index)
                    continue;
                if (this.GetItemType(i) == ItemType.Radio)
                {
                    this.SetItemChecked(i, false);
                    Int32 j = i + 1;
                    while (j < this.Items.Count && this.GetItemType(j) == ItemType.Checkbox)
                    {
                        this.SetItemChecked(j, false);
                        j++;
                    }
                }
            }
        }
        else if (this.GetItemType(ice.Index) == ItemType.Checkbox) // they selected a child; select the root too and deselect other roots and their children
        {
            // Find parent
            Int32 iParentIdx = ice.Index - 1;
            while (iParentIdx >= 0 && this.GetItemType(iParentIdx) == ItemType.Checkbox)
                iParentIdx--;
            this.SetItemChecked(iParentIdx, true);
        }
    }

    protected ItemType GetItemType (Int32 iIdx)
    {
        String strText = this.Items[iIdx].ToString();
        if (strText.StartsWith("(o)"))
            return (ItemType.Radio);
        else if (strText.StartsWith("[x]"))
            return (ItemType.Checkbox);

        throw (new Exception("Invalid item type"));
    }
Walter Williams