Search code examples
c#winformslistboxondrawitem

winform listbox drawitem change substring color


I created a winform custom control which has a textbox and listbox both sharing same bindingsource, so that the listbox can be filtered with the textbox input.

I need to override the lisbox drawitem so that the filtered items having searched text as substring to be of different color or highlighted. (i.e.,)Expected the yellow highlight like below sample image. sample reference

I did as below

private void DrawItemHandler(object sender, DrawItemEventArgs e)
        {
            this.Invoke((MethodInvoker)delegate
            {
                e.DrawBackground();
                e.DrawFocusRectangle();

                string MyString = listBox.GetItemText(listBox.Items[e.Index]);
                string stringToFind = searchInput.Text ;

                if (!string.IsNullOrEmpty(stringToFind))
                {
                    List<int> positions = new List<int>();
                    int pos = 0;
                    while ((pos < MyString.Length) && (pos = MyString.IndexOf(stringToFind, pos, StringComparison.InvariantCultureIgnoreCase)) != -1)
                    {
                        positions.Add(pos);
                        pos += stringToFind.Length;
                    }

                    int c = 0, nLen = 0, width = 0;
                    Rectangle rect = e.Bounds;
                    rect.X = width;
                    do
                    {
                        if (positions.Contains(c))
                        {
                            //int opacity = 128;
                            e.Graphics.DrawString(MyString.Substring(c, stringToFind.Length),
                                                    e.Font,
                                //new SolidBrush(Color.FromArgb(opacity, Color.LightYellow)),
                                                    new SolidBrush(Color.LightYellow),
                                                    rect);
                            nLen = MyString.Substring(c, stringToFind.Length).Length;
                            width += nLen;
                        }
                        else
                        {
                            e.Graphics.DrawString(MyString[c].ToString(),
                            e.Font,
                            new SolidBrush(listBox.ForeColor),
                            rect);
                            nLen = MyString[c].ToString().Length;
                            width += nLen;
                        }
                        rect.X = width;
                    }
                    while ((c += nLen) < MyString.Length);
                }
                else
                {
                    e.Graphics.DrawString(MyString,
                        e.Font,
                        new SolidBrush(listBox.ForeColor),
                        e.Bounds);
                }

            });

        }

and the result was the item text being overwritten characters.

initially after search

I cannot identify the error part, is it in the rectangle bounds or the drawstring part. Also apart from item background color, how can I change the background of substring in the item text. Please help me on this.


Solution

  • Well, the task is not as easy as it should be because neither TextRenderer.MeasureText nor Graphics.MeasureString seems to be very accurate for the job. But using a different overload of Graphics.MeasureString passing the rectangle Width and StringFormat.GenericTypographic it seems to work a little better.

    This is my attempt on your problem, hope it helps:

        private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
        {
            ListBox listBox = (ListBox)sender;
            this.Invoke((MethodInvoker)delegate
            {
                e.DrawBackground();
                e.DrawFocusRectangle();
    
                string MyString = listBox.GetItemText(listBox.Items[e.Index]);
                string stringToFind = searchInput.Text;
    
    
                if (!string.IsNullOrEmpty(stringToFind))
                {
                    string[] strings = MyString.Split(new string[] { stringToFind }, StringSplitOptions.None);
    
                    Rectangle rect = e.Bounds;
    
                    for (int i=0;i<strings.Length;i++)
                    {
                        string s = strings[i];
                        if (s != "")
                        {
                            int width = (int)e.Graphics.MeasureString(s, e.Font,e.Bounds.Width, StringFormat.GenericTypographic).Width;
                            rect.Width = width;
                            TextRenderer.DrawText(e.Graphics, s, e.Font, new Point(rect.X, rect.Y), listBox.ForeColor);
                            rect.X += width;
                        }
    
                        if (i < strings.Length - 1)
                        {
                            int width2 = (int)e.Graphics.MeasureString(stringToFind, e.Font, e.Bounds.Width, StringFormat.GenericTypographic).Width;
                            rect.Width = width2;
                            TextRenderer.DrawText(e.Graphics, stringToFind, e.Font, new Point(rect.X, rect.Y), listBox.ForeColor, Color.Yellow);
                            rect.X += width2;
                        }
                    }
                }
                else
                {
                    TextRenderer.DrawText(e.Graphics, MyString, e.Font, new Point(e.Bounds.X, e.Bounds.Y), listBox.ForeColor);
                }
    
            });
    
    
        }