如何让DrawMode成为DrawMode.OwnerDrawVariable

时间:2011-11-14 05:13:38

标签: c# .net winforms

我试图将Bradley Copers真棒分组的ComboxBox转换为GroupedCheckedListBox。但是在下面的代码中,OnMeasureItem从未进入过。似乎我无法将DrawMode更改为DrawMode.OwnerDrawVariable,保持为Normal。

如果我使用下面的代码扩展ListBox。它工作得很好。我需要自己实现CheckBox的东西吗?

namespace GroupedCheckBoxList.SeoTools.UI.Components
{
    // Based on Bradley Smith's "A ComboBox Control With Grouping"

    class GroupedCheckedListBox : CheckedListBox, IComparer
    {
        private BindingSource mBindingSource;       // used for change detection and grouping
        private Font mGroupFont;                    // for painting
        private string mGroupMember;                // name of group-by property
        private PropertyDescriptor mGroupProperty;  // used to get group-by values
        private ArrayList mInternalItems;           // internal sorted collection of items
        private TextFormatFlags mTextFormatFlags;   // used in measuring/painting

        /// <summary>
        /// Gets or sets the data source for this GroupedComboBox.
        /// </summary>
        public new object DataSource
        {
            get
            {
                // binding source should be transparent to the user
                return (mBindingSource != null) ? mBindingSource.DataSource : null;
            }
            set
            {
                if (value != null)
                {
                    // wrap the object in a binding source and listen for changes
                    mBindingSource = new BindingSource(value, String.Empty);
                    mBindingSource.ListChanged += new ListChangedEventHandler(mBindingSource_ListChanged);
                    SyncInternalItems();
                }
                else
                {
                    // remove binding
                    base.DataSource = mBindingSource = null;
                }
            }
        }

        /// <summary>
        /// Gets a value indicating whether the drawing of elements in the list will be handled by user code. 
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new DrawMode DrawMode
        {
            get
            {
                return base.DrawMode;
            }
            private set
            {
                base.DrawMode = value;
            }
        }

        /// <summary>
        /// Gets or sets the property to use when grouping items in the list.
        /// </summary>
        public string GroupMember
        {
            get { return mGroupMember; }
            set
            {
                mGroupMember = value;
                if (mBindingSource != null) SyncInternalItems();
            }
        }

        /// <summary>
        /// Initialises a new instance of the GroupedCheckedListBox class.
        /// </summary>
        public GroupedCheckedListBox()
        {
            DrawMode = DrawMode.OwnerDrawVariable; //DOESN'T WORK VALUE STAYS AS NORMAL
            mGroupMember = String.Empty;
            mInternalItems = new ArrayList();
            mTextFormatFlags = TextFormatFlags.EndEllipsis | TextFormatFlags.NoPrefix | TextFormatFlags.SingleLine | TextFormatFlags.VerticalCenter;
        }

        /// <summary>
        /// Explicit interface implementation for the IComparer.Compare method. Performs a two-tier comparison 
        /// on two list items so that the list can be sorted by group, then by display value.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        int IComparer.Compare(object x, object y)
        {
            // compare the display values (and return the result if there is no grouping)
            int secondLevelSort = Comparer.Default.Compare(GetItemText(x), GetItemText(y));
            if (mGroupProperty == null) return secondLevelSort;

            // compare the group values - if equal, return the earlier comparison
            int firstLevelSort = Comparer.Default.Compare(
                Convert.ToString(mGroupProperty.GetValue(x)),
                Convert.ToString(mGroupProperty.GetValue(y))
            );

            if (firstLevelSort == 0)
                return secondLevelSort;
            else
                return firstLevelSort;
        }

        /// <summary>
        /// Determines whether the list item at the specified index is the start of a new group. In all 
        /// cases, populates the string respresentation of the group that the item belongs to.
        /// </summary>
        /// <param name="index"></param>
        /// <param name="groupText"></param>
        /// <returns></returns>
        private bool IsGroupStart(int index, out string groupText)
        {
            bool isGroupStart = false;
            groupText = String.Empty;

            if ((mGroupProperty != null) && (index >= 0) && (index < Items.Count))
            {
                // get the group value using the property descriptor
                groupText = Convert.ToString(mGroupProperty.GetValue(Items[index]));

                // this item is the start of a group if it is the first item with a group -or- if
                // the previous item has a different group
                if ((index == 0) && (groupText != String.Empty))
                {
                    isGroupStart = true;
                }
                else if ((index - 1) >= 0)
                {
                    string previousGroupText = Convert.ToString(mGroupProperty.GetValue(Items[index - 1]));
                    if (previousGroupText != groupText) isGroupStart = true;
                }
            }
            return isGroupStart;
        }

        /// <summary>
        /// Re-synchronises the internal sorted collection when the data source changes.
        /// </summary>
        private void mBindingSource_ListChanged(object sender, ListChangedEventArgs e)
        {
            SyncInternalItems();
        }

        /// <summary>
        /// When the control font changes, updates the font used to render group names.
        /// </summary>
        protected override void OnFontChanged(EventArgs e)
        {
            base.OnFontChanged(e);
            mGroupFont = new Font(Font, FontStyle.Bold);
        }

        /// <summary>
        /// When the parent control changes, updates the font used to render group names.
        /// </summary>
        protected override void OnParentChanged(EventArgs e)
        {
            base.OnParentChanged(e);
            mGroupFont = new Font(Font, FontStyle.Bold);
        }

        /// <summary>
        /// Performs custom painting for a list item.
        /// </summary>
        protected override void OnDrawItem(DrawItemEventArgs e)
        {
            base.OnDrawItem(e);

            if ((e.Index >= 0) && (e.Index < Items.Count))
            {
                // get noteworthy states
                bool comboBoxEdit = (e.State & DrawItemState.ComboBoxEdit) == DrawItemState.ComboBoxEdit;
                bool selected = (e.State & DrawItemState.Selected) == DrawItemState.Selected;
                bool noAccelerator = (e.State & DrawItemState.NoAccelerator) == DrawItemState.NoAccelerator;
                bool disabled = (e.State & DrawItemState.Disabled) == DrawItemState.Disabled;
                bool focus = (e.State & DrawItemState.Focus) == DrawItemState.Focus;

                // determine grouping
                string groupText;
                bool isGroupStart = IsGroupStart(e.Index, out groupText) && !comboBoxEdit;
                bool hasGroup = (groupText != String.Empty) && !comboBoxEdit;

                // the item text will appear in a different colour, depending on its state
                Color textColor;
                if (disabled)
                    textColor = SystemColors.GrayText;
                /*
                else if ((comboBoxEdit && Focused && !DroppedDown) || selected)
                    textColor = SystemColors.HighlightText;
                */
                else
                    textColor = ForeColor;

                // items will be indented if they belong to a group
                Rectangle itemBounds = Rectangle.FromLTRB(
                    e.Bounds.X + (hasGroup ? 12 : 0),
                    e.Bounds.Y + (isGroupStart ? (e.Bounds.Height / 2) : 0),
                    e.Bounds.Right,
                    e.Bounds.Bottom
                );
                Rectangle groupBounds = new Rectangle(
                    e.Bounds.X,
                    e.Bounds.Y,
                    e.Bounds.Width,
                    e.Bounds.Height / 2
                );

                if (isGroupStart && selected)
                {
                    // ensure that the group header is never highlighted
                    e.Graphics.FillRectangle(SystemBrushes.Highlight, e.Bounds);
                    e.Graphics.FillRectangle(new SolidBrush(BackColor), groupBounds);
                }
                else
                {
                    // use the default background-painting logic
                    e.DrawBackground();
                }

                // render group header text
                if (isGroupStart) TextRenderer.DrawText(
                    e.Graphics,
                    groupText,
                    mGroupFont,
                    groupBounds,
                    ForeColor,
                    mTextFormatFlags
                );

                // render item text
                TextRenderer.DrawText(
                    e.Graphics,
                    GetItemText(Items[e.Index]),
                    Font,
                    itemBounds,
                    textColor,
                    mTextFormatFlags
                );

                // paint the focus rectangle if required
                if (focus && !noAccelerator)
                {
                    if (isGroupStart && selected)
                    {
                        // don't draw the focus rectangle around the group header
                        ControlPaint.DrawFocusRectangle(e.Graphics, Rectangle.FromLTRB(groupBounds.X, itemBounds.Y, itemBounds.Right, itemBounds.Bottom));
                    }
                    else
                    {
                        // use default focus rectangle painting logic
                        e.DrawFocusRectangle();
                    }
                }
            }
        }

        /// <summary>
        /// Determines the size of a list item.
        /// </summary>
        protected override void OnMeasureItem(MeasureItemEventArgs e)
        {
            base.OnMeasureItem(e);

            e.ItemHeight = Font.Height;

            string groupText;
            if (IsGroupStart(e.Index, out groupText))
            {
                // the first item in each group will be twice as tall in order to accommodate the group header
                e.ItemHeight *= 2;
                e.ItemWidth = Math.Max(
                    e.ItemWidth,
                    TextRenderer.MeasureText(
                        e.Graphics,
                        groupText,
                        mGroupFont,
                        new Size(e.ItemWidth, e.ItemHeight),
                        mTextFormatFlags
                    ).Width
                );
            }
        }

        /// <summary>
        /// Rebuilds the internal sorted collection.
        /// </summary>
        private void SyncInternalItems()
        {
            // locate the property descriptor that corresponds to the value of GroupMember
            mGroupProperty = null;
            foreach (PropertyDescriptor descriptor in mBindingSource.GetItemProperties(null))
            {
                if (descriptor.Name.Equals(mGroupMember))
                {
                    mGroupProperty = descriptor;
                    break;
                }
            }

            // rebuild the collection and sort using custom logic
            mInternalItems.Clear();
            foreach (object item in mBindingSource) mInternalItems.Add(item);
            mInternalItems.Sort(this);

            // bind the underlying ComboBox to the sorted collection
            base.DataSource = mInternalItems;
        }
    }
}

1 个答案:

答案 0 :(得分:1)

CheckedListBox控件似乎不支持所有者绘图。从MSDN documentation,其DrawMode属性“... [始终返回]一个正常的System.Windows.Forms.DrawMode”。

虽然可以使用我的技术调整常规ListBox以显示组中的项目,但复选框版本却不能。

您可能希望在启用TreeView属性的情况下使用CheckBoxes控件;这是下一个最好的事情。