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.
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.
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.
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);
}
});
}